Forráskód Böngészése

[QOI] Add support for RGB images (previously loader always output RGBA).

Also add QOI to CI test suite by roundtripping 8-bit RGB(A) through QOI and checking the hashes match.
Jeroen van Rijn 3 éve
szülő
commit
bf712e9355
2 módosított fájl, 44 hozzáadás és 19 törlés
  1. 15 14
      core/image/qoi/qoi.odin
  2. 29 5
      tests/core/image/test_core_image.odin

+ 15 - 14
core/image/qoi/qoi.odin

@@ -210,11 +210,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 	context.allocator = allocator
 	options := options
 
-	if .alpha_drop_if_present in options || .alpha_premultiply in options {
-		// TODO: Implement.
-		// As stated in image/common, unimplemented options are ignored.
-	}
-
 	if .info in options {
 		options |= {.return_metadata, .do_not_decompress_image}
 		options -= {.info}
@@ -258,19 +253,19 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 
 	img.width    = int(header.width)
 	img.height   = int(header.height)
-	img.channels = 4
+	img.channels = 4 if .alpha_add_if_missing in options else int(header.channels)
 	img.depth    = 8
 
 	if .do_not_decompress_image in options {
+		img.channels = int(header.channels)
 		return
 	}
 
-	bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), 4, 8)
+	bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8)
 
 	if !resize(&img.pixels.buf, bytes_needed) {
 	 	return img, mem.Allocator_Error.Out_Of_Memory
 	}
-	pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
 
 	/*
 		Decode loop starts here.
@@ -278,6 +273,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 	seen: [64]RGBA_Pixel
 	pix := RGBA_Pixel{0, 0, 0, 255}
 	seen[qoi_hash(pix)] = pix
+	pixels := img.pixels.buf[:]
 
 	decode: for len(pixels) > 0 {
 		data := image.read_u8(ctx) or_return
@@ -330,13 +326,13 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 					}
 
 				case .RUN:
-					if length := int(data & 63) + 1; length > len(pixels) {
+					if length := int(data & 63) + 1; (length * img.channels) > len(pixels) {
 						return img, .Corrupt
 					} else {
-						#no_bounds_check for i in 0..<length {
-							pixels[i] = pix
+						#no_bounds_check for in 0..<length {
+							copy(pixels, pix[:img.channels])
+							pixels = pixels[img.channels:]
 						}
-						pixels = pixels[length:]
 					}
 
 					continue decode
@@ -347,8 +343,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 		}
 
 		#no_bounds_check {
-			pixels[0] = pix
-			pixels = pixels[1:]
+			copy(pixels, pix[:img.channels])
+			pixels = pixels[img.channels:]
 		}
 	}
 
@@ -357,6 +353,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 	if trailer_err != nil || trailer != 0x1 {
 		return img, .Missing_Or_Corrupt_Trailer
 	}
+
+	if .alpha_premultiply in options && !image.alpha_drop_if_present(img, options) {
+		return img, .Post_Processing_Error
+	}
+
 	return
 }
 

+ 29 - 5
tests/core/image/test_core_image.odin

@@ -5,7 +5,7 @@
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
 
-	A test suite for PNG.
+	A test suite for PNG + QOI.
 */
 package test_core_image
 
@@ -14,6 +14,7 @@ import "core:testing"
 import "core:compress"
 import "core:image"
 import "core:image/png"
+import "core:image/qoi"
 
 import "core:bytes"
 import "core:hash"
@@ -1499,11 +1500,34 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 
 				passed &= dims_pass
 
-				hash   := hash.crc32(pixels)
-				error  = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, hash, test.hash, test.options)
-				expect(t, test.hash == hash, error)
+				png_hash   := hash.crc32(pixels)
+				error  = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options)
+				expect(t, test.hash == png_hash, error)
+
+				passed &= test.hash == png_hash
+
+				// Roundtrip through QOI to test the QOI encoder and decoder.
+				if passed && img.depth == 8 && (img.channels == 3 || img.channels == 4) {
+					qoi_buffer: bytes.Buffer
+					defer bytes.buffer_destroy(&qoi_buffer)
+					qoi_save_err := qoi.save(&qoi_buffer, img)
+
+					error  = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
+					expect(t, qoi_save_err == nil, error)
+
+					if qoi_save_err == nil {
+						qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
+						defer qoi.destroy(qoi_img)
+
+						error  = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
+						expect(t, qoi_load_err == nil, error)
+
+						qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
+						error  = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
+						expect(t, qoi_hash == png_hash, error)
+					}
+				}
 
-				passed &= test.hash == hash
 				if .return_metadata in test.options {
 
 					if v, ok := img.metadata.(^image.PNG_Info); ok {