mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
548 lines
19 KiB
Zig
548 lines
19 KiB
Zig
const std = @import("std");
|
|
const testing = std.testing;
|
|
const encoder = @import("encoder.zig");
|
|
const pixel_format = @import("pixel_format.zig");
|
|
const PixelFormat = pixel_format.PixelFormat;
|
|
|
|
// Mock testing data creation
|
|
fn createTestImage(allocator: std.mem.Allocator, width: usize, height: usize, format: PixelFormat) ![]u8 {
|
|
const bytes_per_pixel = format.getBytesPerPixel();
|
|
const buffer_size = width * height * bytes_per_pixel;
|
|
|
|
var buffer = try allocator.alloc(u8, buffer_size);
|
|
errdefer allocator.free(buffer);
|
|
|
|
// Fill with a simple gradient pattern
|
|
for (0..height) |y| {
|
|
for (0..width) |x| {
|
|
const pixel_index = (y * width + x) * bytes_per_pixel;
|
|
|
|
switch (format) {
|
|
.Gray => {
|
|
// Simple diagonal gradient
|
|
buffer[pixel_index] = @as(u8, @intCast((x + y) % 256));
|
|
},
|
|
.GrayAlpha => {
|
|
// Gray gradient with full alpha
|
|
buffer[pixel_index] = @as(u8, @intCast((x + y) % 256));
|
|
buffer[pixel_index + 1] = 255; // Full alpha
|
|
},
|
|
.RGB => {
|
|
// Red gradient in x, green gradient in y, blue constant
|
|
buffer[pixel_index] = @as(u8, @intCast(x % 256)); // R
|
|
buffer[pixel_index + 1] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 2] = 128; // B constant
|
|
},
|
|
.RGBA => {
|
|
// RGB gradient with full alpha
|
|
buffer[pixel_index] = @as(u8, @intCast(x % 256)); // R
|
|
buffer[pixel_index + 1] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 2] = 128; // B constant
|
|
buffer[pixel_index + 3] = 255; // Full alpha
|
|
},
|
|
.BGR => {
|
|
// Blue gradient in x, green gradient in y, red constant
|
|
buffer[pixel_index] = 128; // B constant
|
|
buffer[pixel_index + 1] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 2] = @as(u8, @intCast(x % 256)); // R
|
|
},
|
|
.BGRA => {
|
|
// BGR gradient with full alpha
|
|
buffer[pixel_index] = 128; // B constant
|
|
buffer[pixel_index + 1] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 2] = @as(u8, @intCast(x % 256)); // R
|
|
buffer[pixel_index + 3] = 255; // Full alpha
|
|
},
|
|
.ARGB => {
|
|
// ARGB format
|
|
buffer[pixel_index] = 255; // A full
|
|
buffer[pixel_index + 1] = @as(u8, @intCast(x % 256)); // R
|
|
buffer[pixel_index + 2] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 3] = 128; // B constant
|
|
},
|
|
.ABGR => {
|
|
// ABGR format
|
|
buffer[pixel_index] = 255; // A full
|
|
buffer[pixel_index + 1] = 128; // B constant
|
|
buffer[pixel_index + 2] = @as(u8, @intCast(y % 256)); // G
|
|
buffer[pixel_index + 3] = @as(u8, @intCast(x % 256)); // R
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// Utility to save an encoded image to a file for visual inspection
|
|
fn saveToFile(_: std.mem.Allocator, data: []const u8, filename: []const u8) !void {
|
|
const file = try std.fs.cwd().createFile(filename, .{});
|
|
defer file.close();
|
|
|
|
try file.writeAll(data);
|
|
}
|
|
|
|
test "Encode JPEG" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGB image
|
|
const width = 256;
|
|
const height = 256;
|
|
const image_format = PixelFormat.RGB;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// Encode to JPEG with quality 80
|
|
const quality = 80;
|
|
const encoded_jpeg = try encoder.encodeJPEG(allocator, image_data, width, height, image_format, quality);
|
|
|
|
// Verify we got some data back (simple sanity check)
|
|
try testing.expect(encoded_jpeg.len > 0);
|
|
|
|
// Optionally save the file for visual inspection
|
|
// Note: This is normally disabled in automated tests
|
|
if (false) {
|
|
try saveToFile(allocator, encoded_jpeg, "test_output.jpg");
|
|
}
|
|
}
|
|
|
|
test "Encode PNG" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 256;
|
|
const height = 256;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// Encode to PNG
|
|
const encoded_png = try encoder.encodePNG(allocator, image_data, width, height, image_format);
|
|
|
|
// Verify we got some data back
|
|
try testing.expect(encoded_png.len > 0);
|
|
|
|
// Optionally save the file for visual inspection
|
|
// Note: This is normally disabled in automated tests
|
|
if (false) {
|
|
try saveToFile(allocator, encoded_png, "test_output.png");
|
|
}
|
|
}
|
|
|
|
// Test various pixel format conversions
|
|
test "Encode different pixel formats" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test images with different pixel formats
|
|
const width = 100;
|
|
const height = 100;
|
|
const formats = [_]PixelFormat{
|
|
.Gray,
|
|
.GrayAlpha,
|
|
.RGB,
|
|
.RGBA,
|
|
.BGR,
|
|
.BGRA,
|
|
};
|
|
|
|
var test_failures = false;
|
|
|
|
for (formats) |format| {
|
|
const image_data = try createTestImage(allocator, width, height, format);
|
|
|
|
// Set up encoding options
|
|
const options = encoder.EncodingOptions{
|
|
.format = .JPEG,
|
|
.quality = .{ .quality = 85 },
|
|
};
|
|
|
|
// Encode the image
|
|
const encoded_data = encoder.encode(allocator, image_data, width, height, format, options) catch |err| {
|
|
// If this specific format causes an error, note it but continue with other formats
|
|
if (err == error.ImageCreationFailed or err == error.NotImplemented or err == error.UnsupportedColorSpace) {
|
|
std.debug.print("Format {any} encoding failed: {s}\n", .{ format, @errorName(err) });
|
|
test_failures = true;
|
|
continue;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(encoded_data);
|
|
|
|
// Basic validation
|
|
try testing.expect(encoded_data.len > 0);
|
|
|
|
// Verify JPEG signature
|
|
try testing.expect(encoded_data[0] == 0xFF);
|
|
try testing.expect(encoded_data[1] == 0xD8);
|
|
}
|
|
|
|
// If some formats failed but others succeeded, that's OK
|
|
// This makes the test more portable across platforms with different capabilities
|
|
if (test_failures) {
|
|
std.debug.print("Note: Some formats failed but test continued\n", .{});
|
|
}
|
|
}
|
|
|
|
// Test direct transcoding between formats
|
|
test "Transcode PNG to JPEG" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 256;
|
|
const height = 256;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// First encode to PNG
|
|
const png_data = encoder.encodePNG(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("PNG encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(png_data);
|
|
|
|
// Transcode PNG to JPEG
|
|
const jpeg_options = encoder.EncodingOptions{
|
|
.format = .JPEG,
|
|
.quality = .{ .quality = 90 },
|
|
};
|
|
|
|
const transcoded_jpeg = encoder.transcode(
|
|
allocator,
|
|
png_data,
|
|
.PNG,
|
|
.JPEG,
|
|
jpeg_options,
|
|
) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("Transcode not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_jpeg);
|
|
|
|
// Verify JPEG signature
|
|
try testing.expect(transcoded_jpeg.len > 0);
|
|
try testing.expect(transcoded_jpeg[0] == 0xFF);
|
|
try testing.expect(transcoded_jpeg[1] == 0xD8);
|
|
try testing.expect(transcoded_jpeg[2] == 0xFF);
|
|
|
|
// Optionally save the files for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, png_data, "test_original.png");
|
|
try saveToFile(allocator, transcoded_jpeg, "test_transcoded.jpg");
|
|
}
|
|
}
|
|
|
|
// Test round trip transcoding
|
|
test "Transcode Round Trip (PNG -> JPEG -> PNG)" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// First encode to PNG
|
|
const png_data = encoder.encodePNG(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("PNG encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(png_data);
|
|
|
|
// Transcode PNG to JPEG
|
|
const transcoded_jpeg = encoder.transcodeToJPEG(allocator, png_data, 90) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("TranscodeToJPEG not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_jpeg);
|
|
|
|
// Now transcode back to PNG
|
|
const transcoded_png = encoder.transcodeToPNG(allocator, transcoded_jpeg) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("TranscodeToPNG not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_png);
|
|
|
|
// Verify PNG signature
|
|
try testing.expect(transcoded_png.len > 0);
|
|
try testing.expectEqual(@as(u8, 0x89), transcoded_png[0]);
|
|
try testing.expectEqual(@as(u8, 0x50), transcoded_png[1]); // P
|
|
try testing.expectEqual(@as(u8, 0x4E), transcoded_png[2]); // N
|
|
try testing.expectEqual(@as(u8, 0x47), transcoded_png[3]); // G
|
|
|
|
// Optionally save the files for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, png_data, "test_original.png");
|
|
try saveToFile(allocator, transcoded_jpeg, "test_intermediate.jpg");
|
|
try saveToFile(allocator, transcoded_png, "test_roundtrip.png");
|
|
}
|
|
}
|
|
|
|
// Test transcoding with various quality settings
|
|
test "Transcode with different quality settings" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// Encode to PNG
|
|
const png_data = encoder.encodePNG(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("PNG encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(png_data);
|
|
|
|
// Test different quality levels for JPEG
|
|
const qualities = [_]u8{ 30, 60, 90 };
|
|
var jpeg_sizes = [qualities.len]usize{ 0, 0, 0 };
|
|
|
|
for (qualities, 0..) |quality, i| {
|
|
const transcoded_jpeg = encoder.transcodeToJPEG(allocator, png_data, quality) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("TranscodeToJPEG not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_jpeg);
|
|
|
|
// Verify JPEG signature
|
|
try testing.expect(transcoded_jpeg.len > 0);
|
|
try testing.expect(transcoded_jpeg[0] == 0xFF);
|
|
try testing.expect(transcoded_jpeg[1] == 0xD8);
|
|
|
|
// Store size for comparison
|
|
jpeg_sizes[i] = transcoded_jpeg.len;
|
|
|
|
// Optionally save the files for visual inspection
|
|
if (false) {
|
|
const filename = try std.fmt.allocPrint(allocator, "test_quality_{d}.jpg", .{quality});
|
|
defer allocator.free(filename);
|
|
try saveToFile(allocator, transcoded_jpeg, filename);
|
|
}
|
|
}
|
|
|
|
// Verify that higher quality generally means larger file
|
|
// Note: This is a general trend but not guaranteed for all images
|
|
// so we use a loose check
|
|
try testing.expect(jpeg_sizes[0] <= jpeg_sizes[2]);
|
|
}
|
|
|
|
// Test TIFF encoding
|
|
test "Encode TIFF" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// Encode to TIFF
|
|
const encoded_tiff = encoder.encodeTIFF(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("TIFF encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(encoded_tiff);
|
|
|
|
// Verify we got some data back
|
|
try testing.expect(encoded_tiff.len > 0);
|
|
|
|
// Verify TIFF signature (either II or MM for Intel or Motorola byte order)
|
|
try testing.expect(encoded_tiff[0] == encoded_tiff[1]); // Either II or MM
|
|
try testing.expect(encoded_tiff[0] == 'I' or encoded_tiff[0] == 'M');
|
|
|
|
// Check for TIFF identifier (42 in appropriate byte order)
|
|
if (encoded_tiff[0] == 'I') {
|
|
// Little endian (Intel)
|
|
try testing.expectEqual(@as(u8, 42), encoded_tiff[2]);
|
|
try testing.expectEqual(@as(u8, 0), encoded_tiff[3]);
|
|
} else {
|
|
// Big endian (Motorola)
|
|
try testing.expectEqual(@as(u8, 0), encoded_tiff[2]);
|
|
try testing.expectEqual(@as(u8, 42), encoded_tiff[3]);
|
|
}
|
|
|
|
// Optionally save the file for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, encoded_tiff, "test_output.tiff");
|
|
}
|
|
}
|
|
|
|
// Test HEIC encoding
|
|
test "Encode HEIC" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// Encode to HEIC with quality 80
|
|
const encoded_heic = encoder.encodeHEIC(allocator, image_data, width, height, image_format, 80) catch |err| {
|
|
if (err == error.NotImplemented or err == error.DestinationCreationFailed) {
|
|
std.debug.print("HEIC encoder not implemented or not supported on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(encoded_heic);
|
|
|
|
// Verify we got some data back
|
|
try testing.expect(encoded_heic.len > 0);
|
|
|
|
// HEIC files start with ftyp box
|
|
// Check for 'ftyp' marker at position 4-8
|
|
if (encoded_heic.len >= 8) {
|
|
try testing.expectEqual(@as(u8, 'f'), encoded_heic[4]);
|
|
try testing.expectEqual(@as(u8, 't'), encoded_heic[5]);
|
|
try testing.expectEqual(@as(u8, 'y'), encoded_heic[6]);
|
|
try testing.expectEqual(@as(u8, 'p'), encoded_heic[7]);
|
|
}
|
|
|
|
// Optionally save the file for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, encoded_heic, "test_output.heic");
|
|
}
|
|
}
|
|
|
|
// Test transcoding to TIFF
|
|
test "Transcode to TIFF" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// First encode to PNG
|
|
const png_data = encoder.encodePNG(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("PNG encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(png_data);
|
|
|
|
// Transcode PNG to TIFF
|
|
const transcoded_tiff = encoder.transcodeToTIFF(allocator, png_data, .PNG) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("Transcode to TIFF not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_tiff);
|
|
|
|
// Verify TIFF signature
|
|
try testing.expect(transcoded_tiff.len > 0);
|
|
try testing.expect(transcoded_tiff[0] == transcoded_tiff[1]); // Either II or MM
|
|
try testing.expect(transcoded_tiff[0] == 'I' or transcoded_tiff[0] == 'M');
|
|
|
|
// Optionally save the files for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, png_data, "test_original.png");
|
|
try saveToFile(allocator, transcoded_tiff, "test_transcoded.tiff");
|
|
}
|
|
}
|
|
|
|
// Test transcoding to HEIC
|
|
test "Transcode to HEIC" {
|
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const allocator = arena.allocator();
|
|
|
|
// Create test RGBA image
|
|
const width = 200;
|
|
const height = 200;
|
|
const image_format = PixelFormat.RGBA;
|
|
|
|
const image_data = try createTestImage(allocator, width, height, image_format);
|
|
|
|
// First encode to PNG
|
|
const png_data = encoder.encodePNG(allocator, image_data, width, height, image_format) catch |err| {
|
|
if (err == error.NotImplemented) {
|
|
std.debug.print("PNG encoder not implemented on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(png_data);
|
|
|
|
// Transcode PNG to HEIC
|
|
const transcoded_heic = encoder.transcodeToHEIC(allocator, png_data, .PNG, 80) catch |err| {
|
|
if (err == error.NotImplemented or err == error.DestinationCreationFailed) {
|
|
std.debug.print("Transcode to HEIC not implemented or not supported on this platform, skipping test\n", .{});
|
|
return;
|
|
}
|
|
return err;
|
|
};
|
|
defer allocator.free(transcoded_heic);
|
|
|
|
// Verify HEIC signature (look for ftyp marker)
|
|
try testing.expect(transcoded_heic.len > 0);
|
|
|
|
if (transcoded_heic.len >= 8) {
|
|
try testing.expectEqual(@as(u8, 'f'), transcoded_heic[4]);
|
|
try testing.expectEqual(@as(u8, 't'), transcoded_heic[5]);
|
|
try testing.expectEqual(@as(u8, 'y'), transcoded_heic[6]);
|
|
try testing.expectEqual(@as(u8, 'p'), transcoded_heic[7]);
|
|
}
|
|
|
|
// Optionally save the files for visual inspection
|
|
if (false) {
|
|
try saveToFile(allocator, png_data, "test_original.png");
|
|
try saveToFile(allocator, transcoded_heic, "test_transcoded.heic");
|
|
}
|
|
}
|