2012-04-30 4 views
9

La mia applicazione ha bisogno di molti PNG e spesso rovino il mio codice mentre provo a lavorare con loro. Per semplificarmi la vita ho creato una grande immagine PNG in Realword Paint e incollato tutte quelle immagini PNG di piccole dimensioni. Ora ho invece un file. Ora tutto ciò di cui ho bisogno è copiare un PNG su un altro con trasparenza (non chiedete perché), perché ho bisogno di lavorare con ogni immagine in modo indiscusso. Sono un pessimo programmatore quando si tratta di lavorare con le immagini. Sto usando Delphi 7.Come copiare un PNG da altri PNG?

PGNImage.Resize

procedure TPngObject.Resize(const CX, CY: Integer); 
    function Min(const A, B: Integer): Integer; 
    begin 
    if A < B then Result := A else Result := B; 
    end; 
var 
    Header: TChunkIHDR; 
    Line, NewBytesPerRow: Integer; 
    NewHandle: HBitmap; 
    NewDC: HDC; 
    NewImageData: Pointer; 
    NewImageAlpha: Pointer; 
    NewImageExtra: Pointer; 
begin 
    if (CX > 0) and (CY > 0) then 
    begin 
    {Gets some actual information} 
    Header := Self.Header; 

    {Creates the new image} 
    NewDC := CreateCompatibleDC(Header.ImageDC); 
    Header.BitmapInfo.bmiHeader.biWidth := cx; 
    Header.BitmapInfo.bmiHeader.biHeight := cy; 
    NewHandle := CreateDIBSection(NewDC, pBitmapInfo(@Header.BitmapInfo)^, 
     DIB_RGB_COLORS, NewImageData, 0, 0); 
    SelectObject(NewDC, NewHandle); 
    {$IFDEF UseDelphi}Canvas.Handle := NewDC;{$ENDIF} 
    NewBytesPerRow := (((Header.BitmapInfo.bmiHeader.biBitCount * cx) + 31) 
     and not 31) div 8; 

    {Copies the image data} 
    for Line := 0 to Min(CY - 1, Height - 1) do 
     CopyMemory(Ptr(Longint(NewImageData) + (Longint(CY) - 1) * 
     NewBytesPerRow - (Line * NewBytesPerRow)), Scanline[Line], 
     Min(NewBytesPerRow, Header.BytesPerRow)); 

    {Build array for alpha information, if necessary} 
    if (Header.ColorType = COLOR_RGBALPHA) or 
     (Header.ColorType = COLOR_GRAYSCALEALPHA) then 
    begin 
     GetMem(NewImageAlpha, CX * CY); 
     Fillchar(NewImageAlpha^, CX * CY, 255); 
     for Line := 0 to Min(CY - 1, Height - 1) do 
     CopyMemory(Ptr(Longint(NewImageAlpha) + (Line * CX)), 
     AlphaScanline[Line], Min(CX, Width)); 
     FreeMem(Header.ImageAlpha); 
     Header.ImageAlpha := NewImageAlpha; 
    end; 

    {$IFDEF Store16bits} 
    if (Header.BitDepth = 16) then 
    begin 
     GetMem(NewImageExtra, CX * CY); 
     Fillchar(NewImageExtra^, CX * CY, 0); 
     for Line := 0 to Min(CY - 1, Height - 1) do 
     CopyMemory(Ptr(Longint(NewImageExtra) + (Line * CX)), 
     ExtraScanline[Line], Min(CX, Width)); 
     FreeMem(Header.ExtraImageData); 
     Header.ExtraImageData := NewImageExtra; 
    end; 
    {$ENDIF} 

    {Deletes the old image} 
    DeleteObject(Header.ImageHandle); 
    DeleteDC(Header.ImageDC); 

    {Prepares the header to get the new image} 
    Header.BytesPerRow := NewBytesPerRow; 
    Header.IHDRData.Width := CX; 
    Header.IHDRData.Height := CY; 
    Header.ImageData := NewImageData; 

    {Replaces with the new image} 
    Header.ImageHandle := NewHandle; 
    Header.ImageDC := NewDC; 
    end 
    else 
    {The new size provided is invalid} 
    RaiseError(EPNGInvalidNewSize, EInvalidNewSize) 

end; 

SmoothResize di Gustavo Daud

procedure SmoothResize(apng:tpngobject; NuWidth,NuHeight:integer); 
var 
    xscale, yscale   : Single; 
    sfrom_y, sfrom_x  : Single; 
    ifrom_y, ifrom_x  : Integer; 
    to_y, to_x    : Integer; 
    weight_x, weight_y  : array[0..1] of Single; 
    weight     : Single; 
    new_red, new_green  : Integer; 
    new_blue, new_alpha : Integer; 
    new_colortype   : Integer; 
    total_red, total_green : Single; 
    total_blue, total_alpha: Single; 
    IsAlpha    : Boolean; 
    ix, iy     : Integer; 
    bTmp : TPNGObject; 
    sli, slo : pRGBLine; 
    ali, alo: pbytearray; 
begin 
    if not (apng.Header.ColorType in [COLOR_RGBALPHA, COLOR_RGB]) then 
    raise Exception.Create('Only COLOR_RGBALPHA and COLOR_RGB formats' + 
    ' are supported'); 
    IsAlpha := apng.Header.ColorType in [COLOR_RGBALPHA]; 
    if IsAlpha then new_colortype := COLOR_RGBALPHA else 
    new_colortype := COLOR_RGB; 
    bTmp := Tpngobject.CreateBlank(new_colortype, 8, NuWidth, NuHeight); 
    xscale := bTmp.Width/(apng.Width-1); 
    yscale := bTmp.Height/(apng.Height-1); 
    for to_y := 0 to bTmp.Height-1 do begin 
    sfrom_y := to_y/yscale; 
    ifrom_y := Trunc(sfrom_y); 
    weight_y[1] := sfrom_y - ifrom_y; 
    weight_y[0] := 1 - weight_y[1]; 
    for to_x := 0 to bTmp.Width-1 do begin 
     sfrom_x := to_x/xscale; 
     ifrom_x := Trunc(sfrom_x); 
     weight_x[1] := sfrom_x - ifrom_x; 
     weight_x[0] := 1 - weight_x[1]; 

     total_red := 0.0; 
     total_green := 0.0; 
     total_blue := 0.0; 
     total_alpha := 0.0; 
     for ix := 0 to 1 do begin 
     for iy := 0 to 1 do begin 
      sli := apng.Scanline[ifrom_y + iy]; 
      if IsAlpha then ali := apng.AlphaScanline[ifrom_y + iy]; 
      new_red := sli[ifrom_x + ix].rgbtRed; 
      new_green := sli[ifrom_x + ix].rgbtGreen; 
      new_blue := sli[ifrom_x + ix].rgbtBlue; 
      if IsAlpha then new_alpha := ali[ifrom_x + ix]; 
      weight := weight_x[ix] * weight_y[iy]; 
      total_red := total_red + new_red * weight; 
      total_green := total_green + new_green * weight; 
      total_blue := total_blue + new_blue * weight; 
      if IsAlpha then total_alpha := total_alpha + new_alpha * weight; 
     end; 
     end; 
     slo := bTmp.ScanLine[to_y]; 
     if IsAlpha then alo := bTmp.AlphaScanLine[to_y]; 
     slo[to_x].rgbtRed := Round(total_red); 
     slo[to_x].rgbtGreen := Round(total_green); 
     slo[to_x].rgbtBlue := Round(total_blue); 
     if isAlpha then alo[to_x] := Round(total_alpha); 
    end; 
    end; 
    apng.Assign(bTmp); 
    bTmp.Free; 
end; 

Grazie mille, Have a nice day!

+6

Non mi sparare per chiedere, ma perché non solo scaricare tutti i singoli PNG in una risorsa File e caricarli, se necessario, da quello invece? È un file quando hai finito, e viene impacchettato nello spazio delle risorse del tuo eseguibile ... una riga di codice per caricare i PNG memorizzati, facile come non arriva? – LaKraven

+0

@LaKraven Sapevo che qualcuno lo chiederà! :/Ma ho bisogno di affettare il BIG di un PNG per ridurli e lavorare con loro. Come sempre nodoby vuole aiutare qui ... –

+0

Bene, quindi un approccio (anche se non il più elegante, sono sicuro) sarebbe quello di iterare X e Y del segmento, quindi replicare il colore dei pixel (RGBA per mantenere Alpha) sulla nuova istanza del contenitore PNG. Ciò ovviamente richiederebbe conoscere le coordinate superiore, inferiore, sinistra e destra dei "segmenti". – LaKraven

risposta

11

Ecco un codice di esempio modificato da un 'SlicePNG' ("Questa funzione fette di un file di grandi dimensioni PNG (ad esempio un'immagine con tutte le immagini di una barra degli strumenti) in piccoli quadri, di uguali dimensioni") Procedura trovato elsewhere: chiamata

procedure CropPNG(Source: TPNGObject; Left, Top, Width, Height: Integer; 
    out Target: TPNGObject); 

    function ColorToTriple(Color: TColor): TRGBTriple; 
    begin 
    Color := ColorToRGB(Color); 
    Result.rgbtBlue := Color shr 16 and $FF; 
    Result.rgbtGreen := Color shr 8 and $FF; 
    Result.rgbtRed := Color and $FF; 
    end; 

var 
    X, Y: Integer; 
    Bitmap: TBitmap; 
    BitmapLine: PRGBLine; 
    AlphaLineA, AlphaLineB: pngimage.PByteArray; 
begin 
    if (Source.Width < (Left + Width)) or (Source.Height < (Top + Height)) then 
    raise Exception.Create('Invalid position/size'); 

    Bitmap := TBitmap.Create; 
    try 
    Bitmap.Width := Width; 
    Bitmap.Height := Height; 
    Bitmap.PixelFormat := pf24bit; 

    for Y := 0 to Bitmap.Height - 1 do begin 
     BitmapLine := Bitmap.Scanline[Y]; 
     for X := 0 to Bitmap.Width - 1 do 
     BitmapLine^[X] := ColorToTriple(Source.Pixels[Left + X, Top + Y]); 
    end; 

    Target := TPNGObject.Create; 
    Target.Assign(Bitmap); 
    finally 
    Bitmap.Free; 
    end; 

    if Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then begin 
    Target.CreateAlpha; 
    for Y := 0 to Target.Height - 1 do begin 
     AlphaLineA := Source.AlphaScanline[Top + Y]; 
     AlphaLineB := Target.AlphaScanline[Y]; 
     for X := 0 to Target.Width - 1 do 
     AlphaLineB^[X] := AlphaLineA^[X + Left]; 
    end; 
    end; 
end; 

Esempio:

var 
    Png: TPNGObject; 
    CroppedPNG: TPNGobject; 
begin 
    PNG := TPNGObject.Create; 
    PNG.LoadFromFile('..\test.png'); 

    CropPNG(PNG, 30, 10, 60, 50, CroppedPNG); 
    CroppedPNG.SaveToFile('..\croptest.png'); 
+0

OMFG, Grazie = 3, ne possiedo uno :)))) –

+0

@Sertac, ora a pensarci, non sarebbe possibile utilizzare 'StretchDIBits' e modificare solo l'intestazione dell'immagine? – TLama

+2

+1 per la semplicità. @TLama, credo anche che questo potrebbe essere fatto senza l'uso di 'TBitmap', avviando con' CreateBlank' e copiando 'Scanline' e' AlphaScanline' nel nuovo PNG. ma sono troppo pigro per provare effettivamente qualcosa oggi;) – kobik

1

Ho provato a scrivere codice solo su carico a png utilizzando libpng. È orribile lavorare con

Provare a utilizzare imlib2 per eseguire la conversione dei file PNG. ha un Delphi binding, a quanto pare.

Se si rimane davvero bloccati, è possibile utilizzare l'eseguibile separato di Inage Magick per eseguire il ritaglio dell'immagine.

11

Ecco un'altra versione (funziona molto veloce):

procedure CropPNG(Source: TPNGObject; Left, Top, Width, Height: Integer; 
    out Target: TPNGObject); 
var 
    IsAlpha: Boolean; 
    Line: Integer; 
begin 
    if (Source.Width < (Left + Width)) or (Source.Height < (Top + Height)) then 
    raise Exception.Create('Invalid position/size'); 

    Target := TPNGObject.CreateBlank(Source.Header.ColorType, 
    Source.Header.BitDepth, Width, Height); 
    IsAlpha := Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA]; 
    for Line := 0 to Target.Height - 1 do 
    begin 
    if IsAlpha then 
     CopyMemory(Target.AlphaScanline[Line], 
     Ptr(LongInt(Source.AlphaScanline[Line + Top]) + LongInt(Left)), 
     Target.Width); 
    CopyMemory(Target.Scanline[Line], 
     Ptr(LongInt(Source.Scanline[Line + Top]) + LongInt(Left * 3)), 
     Target.Width * 3); 
    end; 
end; 

Nota: Il codice di cui sopra è compatibile con il più recente pngimage versione 1.56+ (che supporta il costruttore CreateBlank)

+0

Se la procedura @Sertac fallisce, proverò questo .. –

+5

@ Il metodo di Sertac non "fallirà". Funziona davvero bene. Ho condiviso solo un'alternativa che non implica un 'TBitmap 'centrale e dovrebbe essere più veloce. – kobik

+0

L'ho provato tempo fa, dico solo che se ci saranno problemi, forse userò il tuo. –