Re: HSL/HSV to RGB
Posted: Mon Sep 11, 2023 5:21 pm
Instead of floats for the multiplication factors, I'm using 16 bit signed integer values which in the end are divided by 16384 (16384 is considered 1).
The signed integer range is [-32768, 32767] so divided by 16384 is a range of [-2, 1.999] in steps of 0.000061 which I believe is accurate enough for working with colors.
The PMADDWD opcode and is what makes the multiplication fast.
It also helps that there's no conversion between integers and floats and the pack instructions are already clipping the output values in the range of [0, 255].
Edit:
See my post below for an updated version which is a bit faster
viewtopic.php?p=607263#p607263
The signed integer range is [-32768, 32767] so divided by 16384 is a range of [-2, 1.999] in steps of 0.000061 which I believe is accurate enough for working with colors.
The PMADDWD opcode and is what makes the multiplication fast.
It also helps that there's no conversion between integers and floats and the pack instructions are already clipping the output values in the range of [0, 255].
Edit:
See my post below for an updated version which is a bit faster
viewtopic.php?p=607263#p607263
Code: Select all
Structure ColorMatrix
m.w[16]
a.l[04]
EndStructure
Procedure SetTransformHSV(*Matrix.ColorMatrix, h.f, s.f, v.f, ChannelOrder = 0)
; ChannelOrder 0 => RGBA (MacOS)
; ChannelOrder 1 => BGRA (Windows)
Protected.f vsu, vsw
s * 0.01
v * 0.01
vsu = v*s*Cos(h*#PI/180)
vsw = v*s*Sin(h*#PI/180)
ClearStructure(*Matrix, ColorMatrix)
*Matrix\m[15] = 16384 ; alpha in -> out multiplier
*Matrix\a[00] = 8192 ; constants to add for rounding
*Matrix\a[01] = 8192
*Matrix\a[02] = 8192
If ChannelOrder = 1
; BGRA
*Matrix\m[12] = (0.299*v + 0.701*vsu + 0.168*vsw)*16384 ; red in red out
*Matrix\m[05] = (0.587*v - 0.587*vsu + 0.330*vsw)*16384 ; green in red out
*Matrix\m[04] = (0.114*v - 0.114*vsu - 0.497*vsw)*16384 ; blue in red out
*Matrix\m[10] = (0.299*v - 0.299*vsu - 0.328*vsw)*16384 ; red in green out
*Matrix\m[03] = (0.587*v + 0.413*vsu + 0.035*vsw)*16384 ; green in green out
*Matrix\m[02] = (0.114*v - 0.114*vsu + 0.292*vsw)*16384 ; blue in green out
*Matrix\m[08] = (0.299*v - 0.300*vsu + 1.25*vsw)*16384 ; red in blue out
*Matrix\m[01] = (0.587*v - 0.588*vsu - 1.05*vsw)*16384 ; green in blue out
*Matrix\m[00] = (0.114*v + 0.886*vsu - 0.203*vsw)*16384 ; blue in blue out
Else
; RGBA
*Matrix\m[00] = (0.299*v + 0.701*vsu + 0.168*vsw)*16384 ; red in red out
*Matrix\m[01] = (0.587*v - 0.587*vsu + 0.330*vsw)*16384 ; green in red out
*Matrix\m[08] = (0.114*v - 0.114*vsu - 0.497*vsw)*16384 ; blue in red out
*Matrix\m[02] = (0.299*v - 0.299*vsu - 0.328*vsw)*16384 ; red in green out
*Matrix\m[03] = (0.587*v + 0.413*vsu + 0.035*vsw)*16384 ; green in green out
*Matrix\m[10] = (0.114*v - 0.114*vsu + 0.292*vsw)*16384 ; blue in green out
*Matrix\m[04] = (0.299*v - 0.300*vsu + 1.25*vsw)*16384 ; red in blue out
*Matrix\m[05] = (0.587*v - 0.588*vsu - 1.05*vsw)*16384 ; green in blue out
*Matrix\m[12] = (0.114*v + 0.886*vsu - 0.203*vsw)*16384 ; blue in blue out
EndIf
EndProcedure
Procedure ApplyTransform(*Matrix.ColorMatrix, *InPixels, *OutPixels, NumPixels)
!mov ecx, [p.v_NumPixels]
!sub ecx, 1
!jc .l1
!pxor xmm5, xmm5
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rax, [p.p_Matrix]
!movdqu xmm2, [rax]
!movdqu xmm3, [rax + 16]
!movdqu xmm4, [rax + 32]
!mov rax, [p.p_InPixels]
!mov rdx, [p.p_OutPixels]
!.l0:
!movd xmm1, [rax + rcx*4] ; load pixel
CompilerElse
!mov eax, [p.p_Matrix]
!movdqu xmm2, [eax]
!movdqu xmm3, [eax + 16]
!movdqu xmm4, [eax + 32]
!mov eax, [p.p_InPixels]
!mov edx, [p.p_OutPixels]
!.l0:
!movd xmm1, [eax + ecx*4] ; load pixel
CompilerEndIf
!punpcklbw xmm1, xmm5 ; zero extend bytes to words (xmm5 = 0)
!pshufd xmm0, xmm1, 0 ; xmm0 [c1c0 c1c0 c1c0 c1c0]
!pshufd xmm1, xmm1, 85 ; xmm1 [c3c2 c3c2 c3c2 c3c2]
!pmaddwd xmm0, xmm2 ; multiply and add
!pmaddwd xmm1, xmm3 ; multiply and add
!paddd xmm0, xmm1 ; add together
!paddd xmm0, xmm4 ; add constant
!psrad xmm0, 14 ; reduce to byte range
!packssdw xmm0, xmm0 ; convert 32s > 16s
!packuswb xmm0, xmm0 ; convert 16s > 8u
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd [rdx + rcx*4], xmm0 ; store pixel
CompilerElse
!movd [edx + ecx*4], xmm0 ; store pixel
CompilerEndIf
!sub ecx, 1
!jnc .l0
!.l1:
EndProcedure
; Create application window
Define m.ColorMatrix
UseJPEGImageDecoder()
UsePNGImageDecoder()
If OpenWindow(0, 0, 0, 910, 530, "HSV Color Transform", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
If CreateStatusBar(0, WindowID(0))
AddStatusBarField(#PB_Ignore)
StatusBarText(0, 0, "Nothing processed yet")
EndIf
ScrollAreaGadget(0, 10, 10, 440, 400, 0, 0, 10, #PB_ScrollArea_Flat|#PB_ScrollArea_Center)
ImageGadget(1, 0, 0, 0, 0, 0)
CloseGadgetList()
ScrollAreaGadget(2, 460, 10, 440, 400, 10, 0, 10, #PB_ScrollArea_Flat|#PB_ScrollArea_Center)
ImageGadget(3, 0, 0, 0, 0, 0)
CloseGadgetList()
TextGadget(4, 12, 430, 98, 30, "Hue rotation")
SpinGadget(5, 10, 450, 100, 30, -180, 360, #PB_Spin_Numeric)
TextGadget(6, 132, 430, 98, 30, "Saturation")
SpinGadget(7, 130, 450, 100, 30, 0, 100, #PB_Spin_Numeric)
TextGadget(8, 252, 430, 98, 30, "Value")
SpinGadget(9, 250, 450, 100, 30, 0, 100, #PB_Spin_Numeric)
SetGadgetState(5, 0)
SetGadgetState(7, 100)
SetGadgetState(9, 100)
ButtonGadget(10, 380, 450, 120, 30, "Apply transform")
ButtonGadget(11, 780, 450, 120, 30, "Load image")
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case 10:
If IsImage(0)
If CreateImage(1, ImageWidth(0), ImageHeight(0), 32) And StartDrawing(ImageOutput(1))
; make a 32 bit copy of the loaded image
DrawingMode(#PB_2DDrawing_AllChannels)
DrawImage(ImageID(0), 0, 0)
; get the buffer address
*PixelBuffer = DrawingBuffer()
PixelCount = OutputHeight()*DrawingBufferPitch() >> 2
; set the transform matrix
If DrawingBufferPixelFormat() = #PB_PixelFormat_32Bits_RGB
SetTransformHSV(@m, GetGadgetState(5), GetGadgetState(7), GetGadgetState(9), 0)
Else
SetTransformHSV(@m, GetGadgetState(5), GetGadgetState(7), GetGadgetState(9), 1)
EndIf
; apply the transform matrix
t1 = ElapsedMilliseconds()
ApplyTransform(@m, *PixelBuffer, *PixelBuffer, PixelCount)
t2 = ElapsedMilliseconds()
StopDrawing()
SetGadgetState(3, ImageID(1))
SetGadgetAttribute(2, #PB_ScrollArea_InnerWidth, ImageWidth(1))
SetGadgetAttribute(2, #PB_ScrollArea_InnerHeight, ImageHeight(1))
StatusBarText(0, 0, "Processed "+Str(PixelCount)+" pixels in "+Str(t2-t1)+" ms")
EndIf
EndIf
Case 11:
File.s = OpenFileRequester("Select image file", "", "Image file | *.png;*.jpg;*.jpeg", 0)
If File And LoadImage(0, File)
SetGadgetState(1, ImageID(0))
SetGadgetAttribute(0, #PB_ScrollArea_InnerWidth, ImageWidth(0))
SetGadgetAttribute(0, #PB_ScrollArea_InnerHeight, ImageHeight(0))
EndIf
EndSelect
EndIf
Until Event = #PB_Event_CloseWindow
EndIf