Now I have a demo which works nearly correct. It is the orginal code form Paul Haeberli, matrix.c.
Thanks to PureBasic, the simpler C, it was easy to convert to PB.
One small error I coudn't not find until know is the hue rotatian angle. Rotate red 120° has to be green but it is blue here.
For testing: go to Procedrue Test() and activate the routine you want. I started with the simple hue rotation without saturation correction!
Code: Select all
; https://graficaobscura.com/matrix/index.html
; this file is a fork of matrix.c from Paul Haeberli from 1993
; https://graficaobscura.com/matrix/matrix.c
; 2023/09/14 S.Maag : translated to PureBasic
EnableExplicit
#RLUM = 0.3086 ; red lumination factor
#GLUM = 0.6094 ; green lumination factor
#BLUM = 0.0820 ; blue lumination factor
; Intel Little Endian RGBA-ByteOrder in Memory
#OFFSET_R = 0
#OFFSET_G = 1
#OFFSET_B = 2
#OFFSET_A = 3
; Big Endian Byte-Order in Memory
; #OFFSET_R = 3
; #OFFSET_G = 2
; #OFFSET_B = 1
; #OFFSET_A = 0
Structure TColorMatrix4f; Color Transformation Matrix, signed 16Bit Integer
m.f[0] ; 0..15
m00.f : m01.f : m02.f : m03.f ; 0..3
m10.f : m11.f : m12.f : m13.f ; 4..7
m20.f : m21.f : m22.f : m23.f ; 12..15
m30.f : m31.f : m32.f : m33.f ; 8..11
EndStructure
Structure TColor
cb.a[0] ; Access as Array 4Bytes [0..3]
Color.l ; Access as 32Bit Long
EndStructure
Procedure applymatrix(*lptr, *mat.TColorMatrix4f, NoOfPixel.i)
; use a matrix To transform colors.
Protected.i ir, ig, ib, r, g, b
Protected *cptr.TColor ; unsigned char
*cptr = *lptr
NoOfPixel-1
While NoOfPixel
ir = *cptr\cb[#OFFSET_R]
ig = *cptr\cb[#OFFSET_G]
ib = *cptr\cb[#OFFSET_B]
With *mat
r = ir * \m00 + ig * \m10 + ib * \m20 + \m30
g = ir * \m01 + ig * \m11 + ib * \m21 + \m31
b = ir * \m02 + ig * \m12 + ib * \m22 + \m32
EndWith
If r <0 : r = 0 : EndIf
If r>255 : r = 255 : EndIf
If g <0 : g = 0 : EndIf
If g >255 : g = 255 : EndIf
If b <0 : b = 0 : EndIf
If b >255 : b = 255 : EndIf
*cptr\cb[#OFFSET_R] = r
*cptr\cb[#OFFSET_G] = g
*cptr\cb[#OFFSET_B] = b
*cptr + SizeOf(Long)
NoOfPixel-1
Wend
ProcedureReturn *mat
EndProcedure
Procedure.i matrixmult_new(*A.TColorMatrix4f, *B.TColorMatrix4f, *C.TColorMatrix4f)
Protected OUT.TColorMatrix4f
OUT\m00 = *A\m00 * *B\m00 + *A\m10 * *B\m01 + *A\m20 * *B\m02 + *A\m30 * *B\m03
OUT\m01 = *A\m01 * *B\m00 + *A\m11 * *B\m01 + *A\m21 * *B\m02 + *A\m31 * *B\m03
OUT\m01 = *A\m02 * *B\m00 + *A\m12 * *B\m01 + *A\m22 * *B\m02 + *A\m32 * *B\m03
OUT\m03 = *A\m03 * *B\m00 + *A\m13 * *B\m01 + *A\m23 * *B\m02 + *A\m33 * *B\m03
OUT\m10 = *A\m00 * *B\m10 + *A\m10 * *B\m11 + *A\m20 * *B\m12 + *A\m30 * *B\m13
OUT\m11 = *A\m01 * *B\m10 + *A\m11 * *B\m11 + *A\m21 * *B\m12 + *A\m31 * *B\m13
OUT\m12 = *A\m02 * *B\m10 + *A\m12 * *B\m11 + *A\m22 * *B\m12 + *A\m32 * *B\m13
OUT\m13 = *A\m03 * *B\m10 + *A\m13 * *B\m11 + *A\m23 * *B\m12 + *A\m33 * *B\m13
OUT\m20 = *A\m00 * *B\m20 + *A\m10 * *B\m21 + *A\m20 * *B\m22 + *A\m30 * *B\m23
OUT\m21 = *A\m01 * *B\m20 + *A\m11 * *B\m21 + *A\m21 * *B\m22 + *A\m31 * *B\m23
OUT\m22 = *A\m02 * *B\m20 + *A\m12 * *B\m21 + *A\m22 * *B\m22 + *A\m32 * *B\m23
OUT\m23 = *A\m03 * *B\m20 + *A\m13 * *B\m21 + *A\m23 * *B\m22 + *A\m33 * *B\m23
OUT\m30 = *A\m00 * *B\m30 + *A\m10 * *B\m31 + *A\m20 * *B\m32 + *A\m30 * *B\m33
OUT\m31 = *A\m01 * *B\m30 + *A\m11 * *B\m31 + *A\m21 * *B\m32 + *A\m31 * *B\m33
OUT\m32 = *A\m02 * *B\m30 + *A\m12 * *B\m31 + *A\m22 * *B\m32 + *A\m32 * *B\m33
OUT\m33 = *A\m03 * *B\m30 + *A\m23 * *B\m31 + *A\m23 * *B\m32 + *A\m33 * *B\m33
CopyStructure(Out, *C, TColorMatrix4f)
ProcedureReturn *C
EndProcedure
Procedure.i matrixmult (*a.TColorMatrix4f, *b.TColorMatrix4f, *c.TColorMatrix4f)
; multiply two matricies
Protected x, y
Protected temp.TColorMatrix4f
; For(y=0; y<4 ; y++)
; For(x=0 ; x<4 ; x++) {
; temp[y][x] = b[y][0] * a[0][x]
; + b[y][1] * a[1][x]
; + b[y][2] * a[2][x]
; + b[y][3] * a[3][x];
For y=0 To 3 ; y<4 ; y++)
For x=0 To 3 ; x<4 ; x++) {
temp\m[y*4 + x] = *b\m[y*4 +0] * *a\m[0+x] + *b\m[y*4 +1] * *a\m[4+x] + *b\m[y*4 +2] * *a\m[8+x] + *b\m[y*4 +3] * *a\m[12+x]
Next
Next
CopyStructure(temp, *c, TColorMatrix4f)
ProcedureReturn
EndProcedure
Procedure.i identmat(*mat.TColorMatrix4f)
; make an identity matrix
With *mat
\m00 = 1 : \m01 = 0 : \m02 = 0 : \m03 = 0
\m10 = 0 : \m11 = 1 : \m12 = 0 : \m13 = 0
\m20 = 0 : \m21 = 0 : \m22 = 1 : \m23 = 0
\m30 = 0 : \m31 = 0 : \m32 = 0 : \m33 = 1
EndWith
ProcedureReturn *mat
EndProcedure
Procedure.i xformpnt(*mat.TColorMatrix4f, x.f, y.f, z.f, *tx.float, *ty.float, *tz.float)
; transform a 3D point using a matrix
With *mat
*tx\f = x * \m00 + y * \m10 + z * \m20 + \m30
*ty\f = x * \m01 + y * \m11 + z * \m21 + \m31
*tz\f = x * \m02 + y * \m12 + z * \m22 + \m32
EndWith
ProcedureReturn *mat
EndProcedure
Procedure.i cscalemat(*mat.TColorMatrix4f , rscale.f ,gscale.f , bscale.f)
; make a color scale marix
Protected mmat.TColorMatrix4f
With mmat
\m00 = rscale : \m01 = 0 : \m02 = 0 : \m03 = 0
\m10 = 0 : \m11 = gscale : \m12 = 0 : \m13 = 0
\m20 = 0 : \m21 = 0 : \m22 = bscale : \m23 = 0
\m30 = 0 : \m31 = 0 : \m32 = 0 : \m33 = 1
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i lummat(*mat.TColorMatrix4f)
; make a luminance matrix
Protected mmat.TColorMatrix4f
Protected.f rwgt, gwgt, bwgt
rwgt = #RLUM
gwgt = #GLUM
bwgt = #BLUM
With mmat
\m00 = rwgt : \m01 = rwgt : \m02 = rwgt : \m03 = 0
\m10 = gwgt : \m11 = gwgt : \m12 = gwgt : \m13 = 0
\m20 = bwgt : \m21 = bwgt : \m22 = bwgt : \m23 = 0
\m30 = 0 : \m31 = 0 : \m32 = 0 : \m33 = 1
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i saturatemat(*mat.TColorMatrix4f, sat.f)
; make a saturation marix
Protected mmat.TColorMatrix4f ; float mmat[4][4];
Protected.f a, b, c, d, e, f, g, h, i
Protected.f rwgt, gwgt, bwgt
rwgt = #RLUM
gwgt = #GLUM
bwgt = #BLUM
a = (1.0-sat)*rwgt + sat
b = (1.0-sat)*rwgt
c = (1.0-sat)*rwgt
d = (1.0-sat)*gwgt
e = (1.0-sat)*gwgt + sat
f = (1.0-sat)*gwgt
g = (1.0-sat)*bwgt
h = (1.0-sat)*bwgt
i = (1.0-sat)*bwgt + sat
With mmat
\m00 = a : \m01 = b : \m02 = c : \m03 = 0
\m10 = d : \m11 = e : \m12 = f : \m13 = 0
\m20 = g : \m21 = h : \m22 = i : \m23 = 0
\m30 = 0 : \m31 = 0 : \m32 = 0 : \m33 = 1
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i offsetmat(*mat.TColorMatrix4f ,roffset.f ,goffset.f ,boffset.f)
; offset r, g, And b
Protected mmat.TColorMatrix4f ; float mmat[4][4];
With mmat
\m00 = 1 : \m01 = 0 : \m02 = 0 : \m03 = 0
\m10 = 0 : \m11 = 1 : \m12 = 0 : \m13 = 0
\m20 = 0 : \m21 = 0 : \m22 = 1 : \m23 = 0
\m30 = roffset : \m31 = goffset : \m32 = boffset : \m33 = 1
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
; HSV ColorSpace hue for a Saturation of 100% and a Value of 100%
; 0 = red : 60 = yellow : 120 = green : 180 = cyan : 240 = blue : 300 = Magenta
; red=HSV(0,1,1) yellow=HSV(60,1,1) green=HSV(120,1,1); blue=HSV(240,1,1) magenta=HSV(300,1,1) purpur=HSV(300,1,0.5) white=HSV(0,0,1) grey=HSV(0,0,0.5)
; but it is left rotation system.
;
; +y grass green (90°)
; |
; |
; |
; |
; <------------------------------------------>
; -x cyan (180°) | +x red (0°)
; |
; |
; |
; -y violet (270°)
;
;
Procedure.i xrotatemat(*mat.TColorMatrix4f, rs.f , rc.f)
; rotate about the x (red) axis
; S.Maag: changed rotation orientation to be compatible with the standard 3D matrix for 3D Grafics
; Rotation X
; | 1 0 0 0 |
; | 0 cos -sin 0 |
; | 0 sin cos 0 |
; | 0 0 0 1 |
Protected mmat.TColorMatrix4f ; float mmat[4][4]
With mmat
\m00 = 1.0
\m01 = 0.0
\m02 = 0.0
\m03 = 0.0
\m10 = 0.0
\m11 = rc
\m12 = -rs ; org: rs
\m13 = 0.0
\m20 = 0.0
\m21 = rs ; org: -rs
\m22 = rc
\m23 = 0.0
\m30 = 0.0
\m31 = 0.0
\m32 = 0.0
\m33 = 1.0
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i yrotatemat(*mat.TColorMatrix4f, rs.f , rc.f)
; rotate about the y (green) axis
; S.Maag: changed rotation orientation to be compatible with the standard 3D matrix for 3D Grafics
; Rotation Y
; | cos 0 sin 0 |
; | 0 1 0 0 |
; |-sin 0 cos 0 |
; | 0 0 0 1 |
Protected mmat.TColorMatrix4f ; float mmat[4][4]
With mmat
\m00 = rc
\m01 = 0.0
\m02 = rs ; org: -rs
\m03 = 0.0
\m10 = 0.0
\m11 = 1.0
\m12 = 0.0
\m13 = 0.0
\m20 = -rs ; org: rs
\m21 = 0.0
\m22 = rc
\m23 = 0.0
\m30 = 0.0
\m31 = 0.0
\m32 = 0.0
\m33 = 1.0
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i zrotatemat(*mat.TColorMatrix4f, rs.f , rc.f)
; rotate about the z (blue) axis
Protected mmat.TColorMatrix4f ; float mmat[4][4]
; S.Maag: changed rotation orientation to be compatible with the standard 3D matrix for 3D Grafics
; Rotation Z (counterclockwise)
; |cos -sin 0 0 |
; |sin cos 0 0 |
; | 0 0 1 0 |
; | 0 0 0 1 |
With mmat
\m00 = rc
\m01 = -rs ; org: rs
\m02 = 0.0
\m03 = 0.0
\m10 = rs ; org: -rs
\m11 = rc
\m12 = 0.0
\m13 = 0.0
\m20 = 0.0
\m21 = 0.0
\m22 = 1.0
\m23 = 0.0
\m30 = 0.0
\m31 = 0.0
\m32 = 0.0
\m33 = 1.0
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i zshearmat(*mat.TColorMatrix4f, dx.f, dy.f)
;shear z using x And y.
Protected mmat.TColorMatrix4f ; float mmat[4][4]
With mmat
\m00 = 1.0 : \m01 = 0.0 : \m02 = dx : \m03 = 0.0
\m10 = 0.0 : \m11 = 1.0 : \m12 = dy : \m13 = 0.0
\m20 = 0.0 : \m21 = 0.0 : \m22 = 1.0 : \m23 = 0.0
\m30 = 0.0 : \m31 = 0.0 : \m32 = 0.0 : \m33 = 1.0
EndWith
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
Procedure.i simplehuerotatemat(*mat.TColorMatrix4f, rot.f)
; simple hue rotation. This changes luminance
Protected.f mag
Protected.f xrs, xrc
Protected.f yrs, yrc
Protected.f zrs, zrc
; S.Maag: changed x and y rotation direction and
; changed xrotmat, yrotmat, zrotmat to the 3D standard rotation matrix
; /* rotate the grey vector into positive Z */
mag = Sqr(2.0)
xrs = -1.0/mag ; org: 1.0/mag
xrc = 1.0/mag
xrotatemat(*mat, xrs, xrc)
mag = Sqr(3.0)
yrs = 1.0/mag; ; org: -1.0/mag
yrc = Sqr(2.0)/mag
yrotatemat(*mat, yrs, yrc)
; /* rotate the hue */
; zrs = Sin(rot* #PI/180.0)
; zrc = Cos(rot* #PI/180.0)
zrs = Sin(Radian(rot))
zrc = Cos(Radian(rot))
zrotatemat(*mat, zrs, zrc)
; /* rotate the grey vector back into place */
yrotatemat(*mat, -yrs, yrc)
xrotatemat(*mat, -xrs, xrc)
EndProcedure
Procedure.i huerotatemat(*mat.TColorMatrix4f, rot.f)
; rotate the hue, While maintaining luminance.
Protected mmat.TColorMatrix4f ; float mmat[4][4]
Protected.f mag
Protected.f lx, ly, lz
Protected.f xrs, xrc
Protected.f yrs, yrc
Protected.f zrs, zrc
Protected.f zsx, zsy
identmat(mmat)
; S.Maag: changed x and y rotation direction and
; changed xrotmat, yrotmat, zrotmat to the 3D standard rotation matrix
; /* rotate the grey vector into positive Z */
mag = Sqr(2.0)
xrs = -1.0/mag ; org: 1.0/mag
xrc = 1.0/mag
xrotatemat(mmat, xrs, xrc)
mag = Sqr(3.0)
yrs = 1.0/mag ; -1.0/mag
yrc = Sqr(2.0)/mag
yrotatemat(mmat, yrs, yrc)
; /* shear the space To make the luminance plane horizontal */
xformpnt(mmat, #RLUM, #GLUM, #BLUM, @lx, @ly, @lz)
zsx = lx/lz
zsy = ly/lz
zshearmat(mmat, zsx, zsy)
; /* rotate the hue */
; zrs = Sin(rot* #PI/180.0)
; zrc = Cos(rot* #PI/180.0)
zrs = Sin(Radian(rot))
zrc = Cos(Radian(rot))
zrotatemat(mmat, zrs, zrc)
; /* unshear the space to put the luminance plane back */
zshearmat(mmat, -zsx, -zsy)
; /* rotate the grey vector back into place */
yrotatemat(mmat, -yrs, yrc)
xrotatemat(mmat, -xrs, xrc)
matrixmult(mmat, *mat, *mat)
ProcedureReturn *mat
EndProcedure
; Create application window
Define mat.TColorMatrix4f
Define Event
Define *PixelBuffer, PixelCount
Define ticks1
Define t1
Define File.s
Procedure Test(*lpBuf, Pixels)
Protected mat.TColorMatrix4f
Protected.f rot, sat, v
rot = GetGadgetState(5)
sat = GetGadgetState(7)/100
v = GetGadgetState(9)/100
Debug "rotation = " + rot
Debug "saturation = " +sat
identmat(mat)
;offsetmat(mat, -50.0, -50.0, -50.0) ; /* offset color */
;cscalemat(mat, 1.4, 1.5, 1.6) ; /* scale the colors */
;saturatemat(mat, sat) ; /* saturate by 2.0 */
;huerotatemat(mat, rot) ; /* rotate the hue 10 */
; first we test the simple HueRotation! But until know there is anywhere a problem, because
simplehuerotatemat(mat, rot)
; printmat(mat)
applymatrix(*lpBuf, mat, Pixels)
EndProcedure
UseJPEGImageDecoder()
UsePNGImageDecoder()
#WindowWith = 1210
#WindowHeight = 1000
#AreaWith = 600
#AreaHeight = 400
If OpenWindow(0, 0, 0, #WindowWith, #WindowHeight, "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, 0, 10, #AreaWith, #AreaHeight, 0, 0, 10, #PB_ScrollArea_Flat|#PB_ScrollArea_Center)
ImageGadget(1, 0, 0, 0, 0, 0)
CloseGadgetList()
ScrollAreaGadget(2, #AreaWith + 2, 10, #AreaWith, #AreaHeight, 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, 200, #PB_Spin_Numeric)
TextGadget(8, 252, 430, 98, 30, "Value")
SpinGadget(9, 250, 450, 100, 30, 0, 200, #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
; apply the transform matrix
t1 = ElapsedMilliseconds()
;PixelCount = ColorTranform(@m, *PixelBuffer, *PixelBuffer, PixelCount)
Test(*PixelBuffer, PixelCount)
t1 = ElapsedMilliseconds()-t1
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(t1)+" ms")
EndIf
EndIf
Case 11:
File = 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