2011-11-21 6 views
5

Sto utilizzando GR32 per disegnare più immagini PNG semitrasparenti. Finora ho usato il seguente metodo:Delphi, GR32 + PngObject: la conversione in Bitmap32 non funziona come previsto

png:= TPNGObject.Create; 
    png.LoadFromFile(...); 
    PaintBox321.Buffer.Canvas.Draw(120, 20, png); 

però ho voluto passare al metodo proposto sul sito GR32 (http://graphics32.org/wiki/FAQ/ImageFormatRelated):

tmp:= TBitmap32.Create; 
    LoadPNGintoBitmap32(tmp, ..., foo); 
    tmp.DrawMode:= dmBlend; 
    PaintBox321.Buffer.Draw(Rect(20, 20, 20+ tmp.Width, 20+tmp.Height), 
    tmp.ClipRect, tmp); 

Mentre il primo metodo funziona perfettamente bene, il secondo - che dovrebbe dare lo stesso risultato - causa un problema molto strano con il canale alfa, vedi l'immagine (che mostra anche il confronto con la stessa immagine "sistemata" in Paint.NET - sia lo sfondo che l'icona sono stati aperti sugli strati dell'editor). L'immagine mostra che Bitmap32 è caricato o disegnato in modo non corretto. Qualche consiglio?

Problem with TBitmap32 alpha channel

- ha aggiunto 22 Nov

ho scoperto che non si tratta di disegnare, si tratta di carico PNG per BMP32. Il salvataggio da BMP32 a PNG genera un'immagine PNG errata, "imbiancata" (quella a sinistra).

+1

presumo che è a causa di "opacità", anche tu hai impostato "tmp.DrawMode: = dmBlend;", non ho usato GR32, ma direi che la differenza è dovuta all'opacità. – ComputerSaysNo

+1

@Dorin Duminica, non lo è. L'esempio sul loro sito web mostra che la modalità dovrebbe essere dmBlend se c'è qualche trasparenza nell'immagine PNG caricata. Dal momento che so che tutte le mie immagini sono trasparenti, non devo fare controlli. – migajek

risposta

9

Il motivo sembra essere che la trasparenza viene applicata due volte all'immagine quando caricata con LoadPNGintoBitmap32, dandogli un aspetto più trasparente e grigiastro (maggiori informazioni in seguito).

Prima la trasparenza:

Questo è il codice dall'originale LoadPNGintoBitmap32, le parti critiche sono contrassegnati con i commenti:

PNGObject := TPngObject.Create; 
PNGObject.LoadFromStream(srcStream); 

destBitmap.Assign(PNGObject); // <--- paint to destBitmap's canvas with transparency (!) 
destBitmap.ResetAlpha;   

case PNGObject.TransparencyMode of // <--- the following code sets the transparency again for the TBitmap32 
{ ... } 

Il destBitmap.Assign fa internamente lo stesso come voi nel vostro approccio precedente: E ti permette di l'immagine PNG si dipinge sulla sua tela. Questa operazione rispetta il canale alfa del PNG. Ma questo non è necessario, poiché il canale alfa è assegnato ai pixel di TBitmap32 in un secondo passaggio!

Ora modificare il codice come segue, parti critiche sono ancora contrassegnate con i commenti:

PNGObject := TPngObject.Create; 
PNGObject.LoadFromStream(srcStream); 

PNGObject.RemoveTransparency; // <--- paint PNG without any transparency... 
destBitmap.Assign(PNGObject); // <--- ...here 
destBitmap.ResetAlpha; 

srcStream.Position:=0; 
PNGObject.LoadFromStream(srcStream); // <--- read the image again to get the alpha channel back 

case PNGObject.TransparencyMode of // <--- this is ok now, the alpha channel now only exists in the TBitmap32 
{ ... } 

La soluzione di cui sopra è inefficiente perché legge l'immagine due volte. Ma mostra perché il tuo secondo approccio produce un'immagine più trasparente.

E per il grigiastro: c'è ancora un problema nel codice originale: destBitmap.Assign riempie prima lo sfondo con clWhite32, quindi dipinge l'immagine in modo trasparente su di esso. E poi LoadPNGintoBitmap32 arriva e aggiunge un altro livello di trasparenza su di esso.

+0

grazie, questa dovrebbe essere una moda di risposta perfetta - non solo il codice corretto, ma anche la spiegazione molto dettagliata di cosa c'era di sbagliato :) – migajek

+1

Hehe, siete i benvenuti :) Era una domanda interessante e ben presentata! –

0

Il problema potrebbe essere che il PNG è stato convertito in modo errato in TBitmap32, perdendo le informazioni sulla trasparenza in transito. È un caso comune con immagini PNG con tavolozza. Altrimenti, non avresti dovuto usare "Bitmap.DrawMode: = dmTransparent" e "OuterColor". Se le informazioni in trasparenza da PNG fossero state trasferite correttamente in TBitmpa32, DrawMode: = dmBlend avrebbe funzionato, senza la necessità di impostare OuterColor.

Ciò che importa di più è come è stato caricato un PNG in TBitmap32. L'immagine TPng da Vcl.Imaging.L'unità pngimage (implementata in Delphi XE2 e versioni successive) può disegnare in modo trasparente su bitmap, preservando ciò che era presente in quei bitmap, combinando i colori usando il PNG alpha layer, ecc., ma non consente di convertire facilmente vari formati di trasparenza PNG (inclusi palettizzati) nel componente alfa di ciascun pixel di TBitmap32. Una volta che TPngImage ha disegnato un'immagine, si ottiene l'RGB combinato per ciascun pixel, ma il componente alfa non viene trasferito alla bitmap di destinazione.

Ci sono routine di supporto disponibili che tentano di caricare un PNG in un TBitmap32 con trasparenza, ma hanno svantaggi:

(1) “LoadPNGintoBitmap32” da http://graphics32.org/wiki/FAQ/ImageFormatRelated - si applica la trasparenza per due volte, in modo che le immagini con i valori alfa diversi da 0 o 255 appariranno in modo diverso rispetto ad altri software (il più evidente su immagini traslucide con effetti di vetro). Questo codice applicherà dapprima alpha a RGB e quindi imposta alpha come layer separato, quindi quando dipingi, alpha verrà nuovamente applicato. È possibile trovare ulteriori informazioni su questo problema qui: Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected . Oltre a ciò, non converte correttamente la trasparenza dalle immagini con tavolozza nello strato alfa di TBitmap32. Impostano manualmente la trasparenza alfa per i pixel di un determinato colore della bitmap di output (renderizzata in RGB) piuttosto che prima di renderizzare in RGB, quindi la trasparenza effettiva viene persa come nell'immagine di esempio quando tutti i pixel bianchi sono trasparenti.

(2) “LoadBitmap32FromPNG” dalla libreria gr32ex: https://code.google.com/archive/p/gr32ex/ - un po 'diversa attuazione lo stesso algoritmo (1), ed ha gli stessi problemi (1).

Quindi, le soluzioni sono:

  1. Non utilizzare TBitmap32; usa Vcl.Imaging.pngimage.TPngImage per disegnare direttamente su bitmap di destinazione (schermo, ecc.) - questo è il modo più compatibile per trattare correttamente con vari formati PNG.
  2. Utilizzare un instradamento di supporto per trasferire informazioni sulla trasparenza da Vcl.Imaging.pngimage.TPngImage a TBitmap32.
  3. Utilizzare la libreria PNG GR32 in grado di caricare in modo nativo un PNG in TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/ Poiché ora si dispone di tutte le informazioni su questo problema, è possibile ottenere la soluzione giusta per l'utente.

come caricare lo strato alfa in un solo passaggio

Heinrich Ulbricht ha fatto un bel suggerimento per rimuovere lo strato di trasparenza prima di paining e poi a leggere di nuovo l'immagine. Per evitare di caricare l'immagine due volte, è possibile salvare il layer alfa prima di chiamare PNGObject.RemoveTransparency. Ecco il codice che applica correttamente il layer alfa e carica l'immagine solo una volta. Sfortunatamente, non funziona con le immagini a tavolo. Se sai come riempire correttamente lo strato alfa di TBitmap32 da qualsiasi immagine di tavolo, senza gli effetti descritti allo Transparent Png to TBitmap32 per favore fammi sapere.

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32; SrcStream: TStream; out AlphaChannelUsed: Boolean); 
var 
    PNGObject: TPngImage; 
    PixelPtr: PColor32; 
    AlphaPtr: PByte; 
    SaveAlpha: PByte; 
    I, AlphaSize: Integer; 
begin 
    AlphaChannelUsed := False; 
    PNGObject := TPngImage.Create; 
    try 
    PNGObject.LoadFromStream(SrcStream); 
    AlphaPtr := PByte(PNGObject.AlphaScanline[0]); 
    if Assigned(AlphaPtr) then 
    begin 
     AlphaSize := PNGObject.Width * PNGObject.Height; 
     if AlphaSize <= 0 then raise Exception.Create('PNG files with zero dimensions are not supported to be loaded to TBitmap32'); 
     GetMem(SaveAlpha, AlphaSize); 
     try 
     Move(AlphaPtr^, SaveAlpha^, AlphaSize); 
     PNGObject.RemoveTransparency; 
     DstBitmap.Assign(PNGObject); 
     DstBitmap.ResetAlpha; 
     PixelPtr := PColor32(@DstBitmap.Bits[0]); 
     AlphaPtr := SaveAlpha; 
     for I := 0 to AlphaSize-1 do 
     begin 
      PixelPtr^ := (PixelPtr^ and $00FFFFFF) or (TColor32(AlphaPtr^) shl 24); 
      Inc(PixelPtr); 
      Inc(AlphaPtr); 
     end; 
     finally 
     FreeMem(SaveAlpha, AlphaSize); 
     end; 
     AlphaChannelUsed := True; 
    end else 
    if PNGObject.TransparencyMode = ptmNone then 
    begin 
     DstBitmap.Assign(PNGObject); 
    end else 
    begin 
     raise Exception.Create('Paletted PNG images are not supported in LoadPNGintoBitmap32, transparency cannot be stored to TBitmap32'); 
    end; 
    finally 
    FreeAndNil(PNGObject); 
    end; 
end;