Premultiply / unpremulitply image (bords noirs)

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
blendman
Messages : 2017
Inscription : sam. 19/févr./2011 12:46

Premultiply / unpremulitply image (bords noirs)

Message par blendman »

Sur le forum anglais, j'ai trouvé un code qui permet de "unpremultiply" (ou de premultiply) des images.

L'intérêt ?
c'est de supprimer les bords noirs (bugs) qu'on peut avoir avec certaines images, par exemple lors d'un ResizeImage() ou avec un SaveImage() et la lib vectordrawing !

Voici le lien vers le code de Wilbert :


Voici son code (en ASM, donc hyper optimisé) :

Code : Tout sélectionner

DeclareModule Premultiply
 
  ; 32 bits RGBA or BGRA pixel buffer
 
  Declare PremultiplyPixels(*PixelBuffer32, NumPixels)
  Declare UnpremultiplyPixels(*PixelBuffer32, NumPixels)
 
EndDeclareModule

Module Premultiply
 
  EnableASM
  DisableDebugger
 
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    Macro rax : eax : EndMacro
    Macro rbx : ebx : EndMacro
    Macro rcx : ecx : EndMacro
    Macro rdx : edx : EndMacro
    Macro rsi : esi : EndMacro
  CompilerEndIf
 
  Procedure PremultiplyPixels(*PixelBuffer32, NumPixels)
    lea rax, [premultiply.l_premultiplyTable]
    mov rdx, *PixelBuffer32
    mov rcx, NumPixels
    push rbx
    push rsi
    mov rsi, rax
    !premultiply.l_premul0:
    movzx eax, word [rdx + 2]
    cmp ah, 255
    !je premultiply.l_premul1
    movzx ebx, byte [rsi + rax]
    mov [rdx + 2], bl
    mov al, [rdx + 1]
    movzx ebx, byte [rsi + rax]
    mov [rdx + 1], bl
    mov al, [rdx]
    movzx ebx, byte [rsi + rax]
    mov [rdx], bl
    !premultiply.l_premul1:
    add rdx, 4
    sub rcx, 1
    !jnz premultiply.l_premul0
    pop rsi
    pop rbx   
  EndProcedure
 
  Procedure UnpremultiplyPixels(*PixelBuffer32, NumPixels)
    lea rax, [premultiply.l_premultiplyTable + 0x10000]
    mov rdx, *PixelBuffer32
    mov rcx, NumPixels
    push rbx
    push rsi
    mov rsi, rax
    !premultiply.l_unpremul0:
    movzx eax, word [rdx + 2]
    cmp ah, 255
    !je premultiply.l_unpremul1
    cmp ah, 0
    !je premultiply.l_unpremul1
    movzx ebx, byte [rsi + rax]
    mov [rdx + 2], bl
    mov al, [rdx + 1]
    movzx ebx, byte [rsi + rax]
    mov [rdx + 1], bl
    mov al, [rdx]
    movzx ebx, byte [rsi + rax]
    mov [rdx], bl
    !premultiply.l_unpremul1:
    add rdx, 4
    sub rcx, 1
    !jnz premultiply.l_unpremul0
    pop rsi
    pop rbx
  EndProcedure
 
  Procedure FillTable()
    lea rdx, [premultiply.l_premultiplyTable]
    push rbx
    mov ebx, 255
    sub ecx, ecx
    !premultiply.l_filltable0:
    movzx eax, ch
    mul cl
    add eax, 127
    div bl
    mov [rdx + rcx], al
    add cx, 1
    !jnz premultiply.l_filltable0
    add rdx, 0x10000
    mov ecx, 0x100
    sub bx, bx
    !premultiply.l_filltable1:
    mov eax, 255
    cmp cl, ch
    !jae premultiply.l_filltable2
    mul cl
    add ax, bx
    div ch
    !premultiply.l_filltable2:
    mov [rdx + rcx], al
    add cl, 1
    !jnz premultiply.l_filltable1
    movzx bx, ch
    add bx, 1
    shr bx, 1
    add ch, 1
    !jnz premultiply.l_filltable1
    pop rbx
  EndProcedure
 
  FillTable()
 
  DataSection
    !premultiply.l_premultiplyTable: times 131072 db 0
  EndDataSection
 
EndModule
Une procédure de l'utilisateur "Chi" (beaucoup moins rapide, mais bon, je la copie au cas où ^^) :

Code : Tout sélectionner

rocedure PreMultiplyAlpha(image)
  StartDrawing(ImageOutput(image))
  DrawingMode(#PB_2DDrawing_AllChannels)
  For y=0 To ImageHeight(image)-1
    For x=0 To ImageWidth(image)-1
      color = Point(x, y)
      Plot(x, y, RGBA( (Red(color) * Alpha(color) + 127) /255, (Green(color) * Alpha(color) + 127) /255, (Blue(color) * Alpha(color) + 127) /255, Alpha(color)))
    Next
  Next
  StopDrawing()
  ProcedureReturn image
EndProcedure

Procedure UnPreMultiplyAlpha(image)
  StartDrawing(ImageOutput(image))
  DrawingMode(#PB_2DDrawing_AllChannels)
  For y=0 To ImageHeight(image)-1
    For x=0 To ImageWidth(image)-1
      color = Point(x, y)
      alpha = Alpha(color)
      If alpha=0 : alpha=1 : EndIf
      Plot(x, y, RGBA( (255 * Red(color) + (Alpha(color) /2)) /alpha, (255 * Green(color) + (Alpha(color) /2)) /alpha, (255 * Blue(color) + (Alpha(color) /2)) /alpha, Alpha(color)))
    Next
  Next
  StopDrawing()
  ProcedureReturn image
EndProcedure

Un de mes anciens codes qui montrait le bug des images prémultipliées créées avec la lib vectordrawing :

Code : Tout sélectionner

; infos : to see the bug with vector drawing saving, now fixed with premultiply procedure :) (code de 2015)

Procedure PreMultiplyAlpha(image)
  If StartDrawing(ImageOutput(image))
    DrawingMode(#PB_2DDrawing_AllChannels)
    For y=0 To ImageHeight(image)-1
      For x=0 To ImageWidth(image)-1
        color = Point(x, y)
        Plot(x, y, RGBA( (Red(color) * Alpha(color) + 127) /255, (Green(color) * Alpha(color) + 127) /255, (Blue(color) * Alpha(color) + 127) /255, Alpha(color)))
      Next
    Next
    StopDrawing()
  EndIf
  ProcedureReturn image
EndProcedure

Procedure UnPreMultiplyAlpha(image)
  If StartDrawing(ImageOutput(image))
    DrawingMode(#PB_2DDrawing_AllChannels)
    For y=0 To ImageHeight(image)-1
      For x=0 To ImageWidth(image)-1
        color = Point(x, y)
        alpha = Alpha(color)
        If alpha=0 : alpha=1 : EndIf
        Plot(x, y, RGBA( (255 * Red(color) + (Alpha(color) /2)) /alpha, (255 * Green(color) + (Alpha(color) /2)) /alpha, (255 * Blue(color) + (Alpha(color) /2)) /alpha, Alpha(color)))
      Next
    Next
    StopDrawing()
  EndIf
  ProcedureReturn image
EndProcedure

Procedure DrawVector()
  
  AddPathCircle(75, 100, 60)     
  VectorSourceColor(RGBA(255, 240, 200, 255))
  FillPath()     
  AddPathCircle(125, 100, 60)
  VectorSourceColor(RGBA(210, 250, 255, 207))
  FillPath()   
  
  ; Dessin opaque sur une couche semi-transparente
  BeginVectorLayer(240)
  AddPathCircle(275, 100, 60)   
  VectorSourceColor(RGBA(255, 240, 240, 255))
  FillPath()       
  AddPathCircle(325, 100, 60)
  VectorSourceColor(RGBA(200,255, 240, 255))
  FillPath()   
  EndVectorLayer()
  
EndProcedure

UsePNGImageEncoder()
UsePNGImageDecoder()

If OpenWindow(0, 0, 0, 400, 400, "VectorDrawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(0, 0, 0, 400, 420)
  
  
  ;    first, we save an image in vectordrawing
  
  If CreateImage(0, GadgetWidth(0), GadgetHeight(0), 32,#PB_Image_Transparent)
    
    ; same with or without those line
    ; --- you can comment those lines, but the result is the same.
    If StartDrawing(ImageOutput(0))
      DrawingMode(#PB_2DDrawing_AllChannels)
      Box(0,0,GadgetWidth(0),GadgetHeight(0),RGBA(0,0,0,0))
      StopDrawing()
    EndIf
    ; -----
    
    If StartVectorDrawing(ImageVectorOutput(0))
      DrawVector()
      StopVectorDrawing()
    EndIf
    file$ = "image09E9E8Z7.png"
    
   ;  tempimg = UnPreMultiplyAlpha(0) ; <----------------------- uncomment this line to see the good result !
    
    If SaveImage(tempimg, File$, #PB_ImagePlugin_PNG) = 0
      MessageRequester("CanvasGadget", "Cannot save image: " + File$)
    EndIf
    
    FreeImage(0)
    
    ; the load the image saved, to see the result
    If LoadImage(0, file$)
    EndIf
   
  EndIf           
  
  ; the we draw the 2 images : the vectordrawing and the loaded image :
  
  If StartVectorDrawing(CanvasVectorOutput(0))
    DrawVector()
    MovePathCursor(0, 210)
    DrawVectorImage(ImageID(0))
    StopVectorDrawing()
  EndIf
  
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
   DeleteFile( file$)
EndIf
A mon avis, l'immense avantage de la procedure de Wilbert, c'est que fred pourrait peut-être s'en servir pour fixer les bugs liés aux images prémulitpliées en sorties :
- bug avec ResizeImage()
- bug avec SaveImage() via la lib vectordrawing
etc...

J'espère que ça sera utile à certains d'entre vous ;).

Pour moi, avoir trouvé cette procédure, ça me corrige des bugs dans 4 de mes logiciels de création d'images ^^ (qui me sortaient parfois des images avec un bord noir.

A+ les amis :)