본문 바로가기

프로그래밍/Delphi

투명 PNG 이미지를 투명하지 않은 다른 이미지 포멧으로 저장하기

1. 개요

  투명한 PNG 이미지를 투명 배경을 흰색 바탕으로 처리한 다른 이미지로 저장해 보자.


2. 증상 분석

  투명 PNG 이미지를 투명부분을 흰색 배경으로 처리한채 다른 이미지포멧(BMP 또는 JPG)로 저장하고자 할 경우에 일반적으로 다음 과 같은 방법을 시도하여 변환을 시도할 것이다.


var
  bmp : TBitmap;
  jpg : TJpegImage;
  png : TPngObject;
begin
  try
    bmp := TBitmap.Create;
    jpg := TJpegImage.Create;
    png := TPngObject;

    png .LoadFromFile(APngFileName);
    bmp.Assign(png);
    jpg.Assign(bmp);
    jpg.SaveToFile(AJpegName);
  finally
    jpg.free;
    png.free;
    bmp.Free;
  end;
end;



위와 같이 처리하게 되면 PNG이미지의 투명한 부분이 아마도 다음과 같이 black으로 나오는 것을 경험하게 될 것이다.


3. 처리방안

  GR32를 이용하여 투명 이미지를 배경을 흰색으로 지정한 다른 이미지로 변경해 보자

  GR32의 TBitmap32도 TBitmap과 마찬가지로 직접 사용하게 되면 동일하게 원치 않는 결과를 얻는데 

  다음 소스를 이용하여 처리해 보도록 하자.


  -라이브러리(GR32_PNG, 기존 Graphics 라이브러리에 포함된 소스는 아님)


//*******************************************************************
//
//  UNIT:     G r 3 2 _ P N G     15.Jan.2005,  19. March 2010
//
//  PNG Graphic Unit for graphics32 library v1.7.x and higher
//  to load and save PNG images (Portable Network Graphics)
//  Tested with graphics32 v1.9.0 2010-03-08
//
//  needs TPNGImage component from Gustavo Daud (✉uol.com.br)
//  http://pngdelphi.sourceforge.net/
//
//  Tested with: graphics32 v1.9.0 2010-03-08
//               PNGImage v1.564 2006-07-25.
//
//  FAQ:
//  Q: Is there a PNG library for GR32 which supports loading and saving
//     an image and its alpha channel at the same time?
//
//  A: Yes, you need TPNGImage component from Gustavo Daud
//     and this unit to load and save PNG images.
//
//  LoadPNGintoBitmap32 code taken from
//  http://graphics32.org/wiki/pub/page/FAQ/ImageFormatRelated
//
//  note: if you use standard picture file dialogs of type TOpenPictureDialog
//        or TSavePictureDialog to load or save PNG pictures, you have to add
//        string   "Portable Network Graphics (*.png)"   and   "*.png"
//        to filter property !
//
//  --- functions and procedures ---
//
//  LoadPNGintoBitmap32           load PNG image file into Bitmap32
//  SaveBitmap32ToPNG             save Bitmap32 to PNG image file
//
//  Bitmap32ToPNG                 convert Bitmap32 to PNG object
//
//*******************************************************************

unit GR32_PNG;


INTERFACE


uses   SysUtils, Classes, Graphics, GR32, PNGImage;


function LoadPNGintoBitmap32 (destBitmap: TBitmap32;
                              srcStream: TStream;
                              out transparent: Boolean): boolean;  overload;


function LoadPNGintoBitmap32 (destBitmap: TBitmap32;
                              filename: String;
                              out transparent: Boolean): boolean;  overload;


function SaveBitmap32ToPNG (sourceBitmap: TBitmap32;
                            transparent: Boolean;
                            bgColor32: TColor32;
                            filename: String;
                            compressionLevel: TCompressionLevel = 9;
interlaceMethod: TInterlaceMethod = imNone): boolean;

function Bitmap32ToPNG (sourceBitmap: TBitmap32;
                        paletted, transparent: Boolean;
                        bgColor: TColor;
                        compressionLevel: TCompressionLevel = 9;
                        interlaceMethod: TInterlaceMethod = imNone): tPNGObject;


IMPLEMENTATION


//*********************************************************
// load PNG image from source stream
// input:   destBitmap: TBitmap32;    destination bitmap
//          srcStream: TStream;       source stream
// output:  transparent: boolean;     =true: alpha channel used
// return:  boolean;                  =true: png image file loaded
//          destBitmap: TBitmap32;    destination bitmap data
//---------------------------------------------------------
function LoadPNGintoBitmap32 (destBitmap: TBitmap32;
                              srcStream: TStream;
                              out transparent: Boolean): boolean;
var
  PNGObject: TPNGObject;
  TransparentColor: TColor32;
  PixelPtr: PColor32;
  AlphaPtr: PByte;
  X, Y: Integer;
begin
  PNGObject := nil;
  try
    result := false;
    // if two images with same size are loaded and 2nd has transparent pixel
    // the color value are not cleared -> this is a BUG
    destBitmap.Clear($FF000000);       // bug correction!
    PNGObject := TPngObject.Create;
    PNGObject.LoadFromStream(srcStream);


    destBitmap.Assign(PNGObject);
//    destBitmap.ResetAlpha;           // bug correction!


    case PNGObject.TransparencyMode of
      ptmPartial:
        begin
          if (PNGObject.Header.ColorType = COLOR_GRAYSCALEALPHA) or
             (PNGObject.Header.ColorType = COLOR_RGBALPHA) then
          begin
            PixelPtr := PColor32(@destBitmap.Bits[0]);
            for Y := 0 to destBitmap.Height - 1 do
            begin
              AlphaPtr := PByte(PNGObject.AlphaScanline[Y]);
              for X := 0 to destBitmap.Width - 1 do
              begin
PixelPtr^ := (PixelPtr^ and $00FFFFFF) or (TColor32(AlphaPtr^) shl 24);
                Inc(PixelPtr);                 Inc(AlphaPtr);               end;
            end;
            transparent := True;
          end;
        end;
      ptmBit:
        begin
          TransparentColor := Color32(PNGObject.TransparentColor);
          PixelPtr := PColor32(@destBitmap.Bits[0]);
          for X := 0 to (destBitmap.Height - 1) * (destBitmap.Width - 1) do
          begin
            if PixelPtr^ = TransparentColor then
              PixelPtr^ := PixelPtr^ and $00FFFFFF;
            Inc(PixelPtr);
          end;
          transparent := True;
        end;
      ptmNone:
        transparent := False;
    end;
    result := true;
  finally
    if Assigned(PNGObject) then PNGObject.Free;
  end;
end;
//*********************************************************
// load PNG image file into Bitmap32
// input:   destBitmap: TBitmap32;    destination bitmap
//          filename: String;         name of PNG image file
// output:  transparent: boolean;     =true: alpha channel used
// return:  boolean;                  =true: png image file loaded
//---------------------------------------------------------
function LoadPNGintoBitmap32 (destBitmap: TBitmap32;
                              filename: String;
                              out transparent: boolean): boolean;
var
  FileStream: TFileStream;
begin
  result := false;
  try
    FileStream := TFileStream.Create(filename, fmOpenRead);
    try
      result := LoadPNGintoBitmap32(destBitmap, FileStream, transparent);
    finally
      FileStream.Free;
    end;
  except
  end;
end;


//*********************************************************
// convert Bitmap32 to PNG image
// input:   sourceBitmap      source bitmap 32 bit
//          paletted          =true: PixelFormat is pf8bit
//          transparent       =true: transparent pixels
//          bgColor           background color
//          compressionLevel  compression level, range 0..9, default = 9
//          interlaceMethod   interlaced method, use imNone or imAdam7
// return:  tPNGObject        PNG image object
//---------------------------------------------------------
function Bitmap32ToPNG (sourceBitmap: TBitmap32;
                        paletted, transparent: Boolean;
                        bgColor: TColor;
                        compressionLevel: TCompressionLevel = 9;
                        interlaceMethod: TInterlaceMethod = imNone): tPNGObject;
var
  bm: TBitmap;
  png: TPngObject;
  TRNS: TCHUNKtRNS;
  p: pngImage.PByteArray;
  x, y: Integer;
begin
  Result := nil;
  png := TPngObject.Create;
  try
    bm := TBitmap.Create;
    try
      bm.Assign (sourceBitmap);        // convert data into bitmap
      // force paletted on TBitmap, transparent for the web must be 8bit
      if paletted then
        bm.PixelFormat := pf8bit;
      png.interlaceMethod := interlaceMethod;
      png.compressionLevel := compressionLevel;
      png.Assign(bm);                  // convert bitmap into PNG
    finally
      FreeAndNil(bm);
    end;
    if transparent then begin
      if png.Header.ColorType in [COLOR_PALETTE] then begin
        if (png.Chunks.ItemFromClass(TChunktRNS) = nil) then png.CreateAlpha;
        TRNS := png.Chunks.ItemFromClass(TChunktRNS) as TChunktRNS;
        if Assigned(TRNS) then TRNS.TransparentColor := bgColor;
      end;
if png.Header.ColorType in [COLOR_RGB, COLOR_GRAYSCALE] then png.CreateAlpha;
      if png.Header.ColorType in [COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA] then
      begin
        for y := 0 to png.Header.Height - 1 do begin
          p := png.AlphaScanline[y];
          for x := 0 to png.Header.Width - 1
do p[x] := AlphaComponent(sourceBitmap.Pixel[x,y]); // TARGB(bm.Pixel[x,y]).a;
        end;
      end;
    end;
    Result := png;
  except
    png.Free;
  end;
end;


//*********************************************************
// save Bitmap32 to PNG image file
// input:   srcBitmap         source bitmap
//          transparent       =true: transparent pixels
//          bgColor32         background color 32 bit
//          compressionLevel  compression level, range 0..9, default = 9
//          interlaceMethod   interlaced method, use imNone or imAdam7
// return:  boolean           =true: bitmap saved as PNG image file
//---------------------------------------------------------
function SaveBitmap32ToPNG (sourceBitmap: TBitmap32;
                            transparent: Boolean;
                            bgColor32: TColor32;
                            filename: String;
                            compressionLevel: TCompressionLevel = 9;
interlaceMethod: TInterlaceMethod = imNone): boolean;
var  png: tPNGObject;
begin
  result := false;
  try
    png := Bitmap32ToPNG (sourceBitmap, false, transparent, WinColor(bgColor32),
                          compressionLevel, interlaceMethod);
    try
      png.SaveToFile (filename);
      result := true;
    finally
      png.Free;
    end;
  except
  end;
end;
//---------------------------------------------------------
// file stream variant to save bitmap32 as PNG picture file
//---------------------------------------------------------
(*
function xSaveBitmap32ToPNG (sourceBitmap: TBitmap32;
                            transparent: Boolean;
                            bgColor32: TColor32;
                            filename: String;
                            compressionLevel: TCompressionLevel = 9;
interlaceMethod: TInterlaceMethod = imNone): boolean;
var  png: tPNGObject;
     FileStream: TFileStream;
begin
  result := false;
  try
    FileStream := TFileStream.Create(filename, fmCreate);
    try
png := Bitmap32ToPNG (sourceBitmap, false, transparent, WinColor(bgColor32),
                            compressionLevel, interlaceMethod);
      try
        png.SaveToStream (FileStream);
        result := true;
      finally
        png.Free;
      end;
    finally
      FileStream.Free;
    end;
  except
  end;
end;
*)


//*********************************************************
// code example of loading and saving a PNG image file
//---------------------------------------------------------
procedure PNGExample;
var  transparent: boolean;
     myBitmap: tBitmap32;
begin
  myBitmap := TBitmap32.Create;
  transparent := true;
  if LoadPNGintoBitmap32 (MyBitmap, 'example1.png', transparent)
  then begin
    if transparent then
      // add anything else that should be on transparent PNG image...
      MyBitmap.DrawMode := dmBlend
    else
      MyBitmap.DrawMode := dmOpaque;


    SaveBitmap32ToPNG (myBitmap, transparent, clBlack, 'savetest.png', 5);
  end;
  myBitmap.Free;
end;


begin
  // PNGExample;     // delete comment to test loading and saving PNG image
end.


처리 예제)


var
  bmp : TBitmap;
  bmp32 : TBitmap32;
  jpg : TJpegImage;
  trans : Boolean;
begin
    bmp := TBitmap.Create;
    bmp32 := TBitmap32.Create;
    jpg := TJpegImage.Create;
    try
      LoadPNGintoBitmap32(bmp32, AImageFileName, trans);
      bmp.Assign(bmp32);
      jpg.Assign(bmp);
      jpg.CompressionQuality := 80;
      jpg.SaveToFile(newFn);
    finally
      jpg.Free;
      bmp.Free;
      bmp32.Free;
    end;
end;