Quick OK Image format
Quick OK Image format
This looks like an excellent image format that some of you might enjoy. It boasts 20-50x faster compression than PNG, and 3-4x faster decompression. Worth looking into, if you need faster images.
https://phoboslab.org/log/2021/11/qoi-f ... ompression
https://phoboslab.org/log/2021/11/qoi-f ... ompression
Re: Quick OK Image format
Thanks Tenaja.
That's a very simple and clever way to encode / decode a 24 or 32 bit image.
That's a very simple and clever way to encode / decode a 24 or 32 bit image.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
Re: Quick OK Image format
Interesting read
Re: Quick OK Image format
The thread title says 'quick' instead of 'quite'.
Re: Quick OK Image format
He titled it quite ok, and I read in his benchmark that it was quick...
- NicTheQuick
- Addict
- Posts: 1227
- Joined: Sun Jun 22, 2003 7:43 pm
- Location: Germany, Saarbrücken
- Contact:
Re: Quick OK Image format
There is also Lossless JPEG which is quite easy to understand too. Would be interesting to also check how fast it is against QOI.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
Re: Quick OK Image format
One of the points of the blog post is to bring attention to the bloat that comes along with a committee-driven format.
Re: Quick OK Image format
I'm working on a PureBasic implementation of the QOI algorithm.
Is anybody interested? If so, I'll post it when I'm done.
Is anybody interested? If so, I'll post it when I'm done.
Re: Quick OK Image format
Would be great!I'm working on a PureBasic implementation of the QOI algorithm.
Is anybody interested? If so, I'll post it when I'm done.
-
- Enthusiast
- Posts: 108
- Joined: Wed May 13, 2009 8:38 am
- Location: Arizona, USA
Re: Quick OK Image format
That would be helpful to have in my library. Thank You!I'm working on a PureBasic implementation of the QOI algorithm.
Is anybody interested? If so, I'll post it when I'm done.
Re: Quick OK Image format
Is there a binary to test the compression of our own images? I can only see source code at that site?
Re: Quick OK Image format
Here is my PureBasic implementation of the QOI format encode/decode algorithm.
I wrote it in a 32-bit version of PB, but I don't think there will be any problem running it in 64-bit, as long as you don't use image files with more than 2GB of data.
Initially, I wrote PB code to match the C code as closely as possible. Then I went back and did some optimization where I thought it would make the code faster. Let me know If you have any suggestions (be kind please ).
The test stub at the end of the code converts between BMP and QOI format image files.
I wrote it in a 32-bit version of PB, but I don't think there will be any problem running it in 64-bit, as long as you don't use image files with more than 2GB of data.
Initially, I wrote PB code to match the C code as closely as possible. Then I went back and did some optimization where I thought it would make the code faster. Let me know If you have any suggestions (be kind please ).
The test stub at the end of the code converts between BMP and QOI format image files.
Code: Select all
; Structure qoi_header_t
; Magic.s{4} ; magic bytes "qoif"
; Width.l ; image width in pixels (BE)
; Height.l ; image height in pixels (BE)
; channels.a ; must be 3 (RGB) or 4 (RGBA)
; colorspace.a ; a bitmap 0000rgba where
; ; a zero bit indicates sRGBA,
; ; a one bit indicates linear (user interpreted)
; ; colorspace for each channel
; EndStructure
#QOI_SRGB = $00
#QOI_SRGB_LINEAR_ALPHA = $01
#QOI_LINEAR = $0F
Structure qoi_desc
Width.l
Height.l
channels.a
colorspace.a
EndStructure
Structure rgba
r.a
g.a
B.a
a.a
EndStructure
Structure qoi_rgba_t
StructureUnion
_rgba.rgba
v.l
EndStructureUnion
EndStructure
Macro QOI_MALLOC(sz)
AllocateMemory(sz)
EndMacro
Macro QOI_FREE(p)
FreeMemory(p)
EndMacro
Macro QOI_COLOR_HASH(c)
(Red(c) ! Green(c) ! Blue(c) ! Alpha(c))
EndMacro
#QOI_INDEX = %00000000
#QOI_RUN_8 = %01000000
#QOI_RUN_16 = %01100000
#QOI_DIFF_8 = %10000000
#QOI_DIFF_16 = %11000000
#QOI_DIFF_24 = %11100000
#QOI_COLOR = %11110000
#QOI_MASK_2 = %11000000
#QOI_MASK_3 = %11100000
#QOI_MASK_4 = %11110000
#QOI_MAGIC = 'q' << 24 | 'o' << 16 | 'i' << 8 | 'f'
#QOI_HEADER_SIZE = 14
#QOI_PADDING = 4
Procedure qoi_write_32(*Bytes, *p, v.l)
; retrieve/update pointer
p = PeekL(*p)
PokeL(*p, p + 4)
p + *Bytes
PokeB(p + 0, ($FF000000 & v) >> 24)
PokeB(p + 1, ($00FF0000 & v) >> 16)
PokeB(p + 2, ($0000FF00 & v) >> 8)
PokeB(p + 3, ($000000FF & v))
EndProcedure
Procedure qoi_read_32(*Bytes, *p)
; retrieve/update pointer
p = PeekL(*p)
PokeL(*p, p + 4)
p + *Bytes
a.a = PeekB(p + 0)
B.a = PeekB(p + 1)
c.a = PeekB(p + 2)
d.a = PeekB(p + 3)
ProcedureReturn (a << 24) | (B << 16) | (c << 8) | d
EndProcedure
Procedure.l qoi_decode(*Data, Size.l, *desc.qoi_desc, channels.l)
If *Data = 0 Or *desc = 0 Or (channels <> 0 And channels <> 3 And channels <> 4) Or Size < #QOI_HEADER_SIZE + #QOI_PADDING
ProcedureReturn 0
EndIf
p.l = 0
header_magic.l = qoi_read_32(*Data, @p)
*desc\Width = qoi_read_32(*Data, @p)
*desc\Height = qoi_read_32(*Data, @p)
*desc\channels = PeekB(*Data + p)
p + 1
*desc\colorspace = PeekB(*Data + p)
p + 1
If *desc\Width = 0 Or *desc\Height = 0 Or *desc\channels < 3 Or *desc\channels > 4 Or header_magic <> #QOI_MAGIC
ProcedureReturn 0
EndIf
If channels = 0
channels = *desc\channels
EndIf
px_len.l = *desc\Width * *desc\Height * channels
*pixels = QOI_MALLOC(px_len)
If *pixels = 0
ProcedureReturn 0
EndIf
px.qoi_rgba_t
px\_rgba\a = 255
Dim index.qoi_rgba_t(63)
run.l = 0
chunks_len.l = Size - #QOI_PADDING
px_pos.l = 0
While px_pos < px_len
If run > 0
run - 1
ElseIf p < chunks_len
b1.a = PeekB(*Data + p)
p + 1
If (b1 & #QOI_MASK_2) = #QOI_INDEX
px\v = index(b1 ! #QOI_INDEX)\v
ElseIf (b1 & #QOI_MASK_3) = #QOI_RUN_8
run = b1 & $1F
ElseIf (b1 & #QOI_MASK_3) = #QOI_RUN_16
b2.a = PeekB(*Data + p)
p + 1
run = ((b1 & $1F) << 8 | b2) + 32
ElseIf (b1 & #QOI_MASK_2) = #QOI_DIFF_8
px\_rgba\r + ((b1 >> 4) & $03) - 2
px\_rgba\g + ((b1 >> 2) & $03) - 2
px\_rgba\B + ( b1 & $03) - 2
ElseIf (b1 & #QOI_MASK_3) = #QOI_DIFF_16
b2.a = PeekB(*Data + p)
p + 1
px\_rgba\r + (b1 & $1F) - 16
px\_rgba\g + (b2 >> 4) - 8
px\_rgba\B + (b2 & $0F) - 8
ElseIf (b1 & #QOI_MASK_4) = #QOI_DIFF_24
b2.a = PeekB(*Data + p)
p + 1
b3.a = PeekB(*Data + p)
p + 1
px\_rgba\r + (((b1 & $0F) << 1) | (b2 >> 7)) - 16
px\_rgba\g + ((b2 & $7C) >> 2) - 16
px\_rgba\B + (((b2 & $03) << 3) | ((b3 & $E0) >> 5)) - 16
px\_rgba\a + (b3 & $1F) - 16
ElseIf (b1 & #QOI_MASK_4) = #QOI_COLOR
If b1 & 8
px\_rgba\r = PeekB(*Data + p)
p + 1
EndIf
If b1 & 4
px\_rgba\g = PeekB(*Data + p)
p + 1
EndIf
If b1 & 2
px\_rgba\B = PeekB(*Data + p)
p + 1
EndIf
If b1 & 1
px\_rgba\a = PeekB(*Data + p)
p + 1
EndIf
EndIf
index(QOI_COLOR_HASH(px) % 64)\v = px\v
EndIf
PokeB(*pixels + px_pos, px\_rgba\r)
PokeB(*pixels + px_pos + 1, px\_rgba\g)
PokeB(*pixels + px_pos + 2, px\_rgba\B)
If channels = 4
PokeB(*pixels + px_pos + 3, px\_rgba\a)
EndIf
px_pos + channels
Wend
ProcedureReturn *pixels
EndProcedure
Procedure.l qoi_encode(*Data, *desc.qoi_desc, *out_len)
If *Data = 0 Or *out_len = 0 Or *desc = 0 Or *desc\Width = 0 Or *desc\Height = 0 Or *desc\channels < 3 Or *desc\channels > 4 Or (*desc\colorspace & $F0) <> 0
ProcedureReturn 0
EndIf
max_size.l = *desc\Width * *desc\Height * (*desc\channels + 1) + #QOI_HEADER_SIZE + #QOI_PADDING
p.l = 0
*Bytes = QOI_MALLOC(max_size)
If *Bytes = 0
ProcedureReturn 0
EndIf
qoi_write_32(*Bytes, @p, #QOI_MAGIC)
qoi_write_32(*Bytes, @p, *desc\Width)
qoi_write_32(*Bytes, @p, *desc\Height)
PokeB(*Bytes + p, *desc\channels)
p + 1
PokeB(*Bytes + p, *desc\colorspace)
p + 1
Dim index.qoi_rgba_t(63)
run.l = 0
px_prev.qoi_rgba_t
px_prev\_rgba\a = 255
px.qoi_rgba_t = px_prev
px_len.l = *desc\Width * *desc\Height * *desc\channels
px_end.l = px_len - *desc\channels
channels.l = *desc\channels
px_pos.l = 0
While px_pos < px_len
Data_px.l = *Data + px_pos
With px
\_rgba\r = PeekB(Data_px + 0)
\_rgba\g = PeekB(Data_px + 1)
\_rgba\B = PeekB(Data_px + 2)
If channels = 4
\_rgba\a = PeekB(Data_px + 3)
EndIf
EndWith
If px\v = px_prev\v
run + 1
EndIf
If run > 0 And (run = $2020 Or px\v <> px_prev\v Or px_pos = px_end)
If run < 33
run - 1
PokeB(*Bytes + p, #QOI_RUN_8 | run)
p + 1
Else
run - 33
PokeB(*Bytes + p, #QOI_RUN_16 | run >> 8)
p + 1
PokeB(*Bytes + p, run)
p + 1
EndIf
run = 0
EndIf
If px\v <> px_prev\v
index_pos.l = QOI_COLOR_HASH(px) % 64
If index(index_pos)\v = px\v
PokeB(*Bytes + p, #QOI_INDEX | index_pos)
p + 1
Else
index(index_pos)\v = px\v
vr.l = px\_rgba\r - px_prev\_rgba\r
vg.l = px\_rgba\g - px_prev\_rgba\g
vb.l = px\_rgba\B - px_prev\_rgba\B
va.l = px\_rgba\a - px_prev\_rgba\a
If vr > -17 And vr < 16 And vg > -17 And vg < 16 And vb > -17 And vb < 16 And va > -17 And va < 16
If va = 0 And vr > -3 And vr < 2 And vg > -3 And vg < 2 And vb > -3 And vb < 2
PokeB(*Bytes + p, #QOI_DIFF_8 | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2))
p + 1
ElseIf va = 0 And vr > -17 And vr < 16 And vg > -9 And vg < 8 And vb > -9 And vb < 8
PokeB(*Bytes + p, #QOI_DIFF_16 | (vr + 16))
p + 1
PokeB(*Bytes + p, (vg + 8) << 4 | (vb + 8))
p + 1
Else
PokeB(*Bytes + p, #QOI_DIFF_24 | (vr + 16) >> 1)
p + 1
PokeB(*Bytes + p, (vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3)
p + 1
PokeB(*Bytes + p, (vb + 16) << 5 | (va + 16))
p + 1
EndIf
Else
vv.l = 0
; save pointer for color byte
p_color.l = p
p + 1
If vr
vv | 8
PokeB(*Bytes + p, px\_rgba\r)
p + 1
EndIf
If vg
vv | 4
PokeB(*Bytes + p, px\_rgba\g)
p + 1
EndIf
If vb
vv | 2
PokeB(*Bytes + p, px\_rgba\B)
p + 1
EndIf
If va
vv | 1
PokeB(*Bytes + p, px\_rgba\a)
p + 1
EndIf
PokeB(*Bytes + p_color, #QOI_COLOR | vv)
EndIf
EndIf
EndIf
px_prev\v = px\v
px_pos + channels
Wend
For i.l = 0 To #QOI_PADDING - 1
PokeB(*Bytes + p, 0)
p + 1
Next
PokeL(*out_len, p)
ProcedureReturn *Bytes
EndProcedure
Procedure.l qoi_write(Filename.s, *Data, *desc.qoi_desc)
hFile.l = CreateFile(#PB_Any, Filename)
If hFile = 0
ProcedureReturn 0
EndIf
Size.l
*encoded = qoi_encode(*Data, *desc, @Size)
If *encoded = 0
CloseFile(hFile)
ProcedureReturn 0
EndIf
WriteData(hFile, *encoded, Size)
CloseFile(hFile)
QOI_FREE(*encoded)
ProcedureReturn Size
EndProcedure
Procedure.l qoi_read(Filename.s, *desc.qoi_desc, channels.l)
hFile.l = OpenFile(#PB_Any, Filename)
If hFile = 0
ProcedureReturn 0
EndIf
Size.l = Lof(hFile)
*Data = QOI_MALLOC(Size)
If *Data = 0
ProcedureReturn 0
EndIf
bytes_read.l = ReadData(hFile, *Data, Size)
CloseFile(hFile)
*pixels = qoi_decode(*Data, bytes_read, *desc, channels)
QOI_FREE(*Data)
ProcedureReturn *pixels
EndProcedure
;- test stub to convert between BMP and QOI format
CompilerIf #PB_Compiler_IsMainFile
Filename.s = OpenFileRequester("Select Image File", "C:\", "Bitmap Files (*.bmp)|*.bmp|QOI Files (*.qoi)|*.qoi", 0)
If Filename
Select UCase(GetExtensionPart(Filename))
Case "BMP"
hImage.l = LoadImage(#PB_Any, Filename)
If hImage
StartDrawing(ImageOutput(hImage))
*ImageAddress = DrawingBuffer()
desc.qoi_desc
With desc
\Width = ImageWidth(hImage)
\Height = ImageHeight(hImage)
Select ImageDepth(hImage)
Case 24
\channels = 3
Case 32
\channels = 4
EndSelect
\colorspace = #QOI_SRGB
EndWith
Size.l = qoi_write(Left(Filename, Len(Filename) - 4) + "_Q.qoi", *ImageAddress, @desc)
StopDrawing()
If Size = 0
MessageRequester("Error!", "Couldn't encode/write!", #MB_ICONERROR)
EndIf
EndIf
Case "QOI"
*pixels = qoi_read(Filename, @desc.qoi_desc, 0)
If *pixels = 0
MessageRequester("Error!", "Couldn't read/decode!", #MB_ICONERROR)
Else
channels.l = desc\channels
Select channels
Case 3
depth.l = 24
Case 4
depth.l = 32
EndSelect
W.l = desc\Width
H.l = desc\Height
hImage.l = CreateImage(#PB_Any, W, H, depth)
If hImage
StartDrawing(ImageOutput(hImage))
*ImageAddress = DrawingBuffer()
CopyMemory(*pixels, *ImageAddress, W * H * channels)
StopDrawing()
SaveImage(hImage, Left(Filename, Len(Filename) - 3) + "bmp")
EndIf
QOI_FREE(*pixels)
EndIf
EndSelect
EndIf
CompilerEndIf
Re: Quick OK Image format
A little warning ...
The website mentions
The website mentions
the file format is not yet finalized. We're still working to fix some smaller issues. The final specification will be announced on 2021.12.20.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
Re: Quick OK Image format
Yes, but more importantly... Does it work for your project. The whole point of it is to free your images from a bloated & slow "standardized file format".