mirror of
https://github.com/oven-sh/bun
synced 2026-02-13 20:39:05 +00:00
cool
This commit is contained in:
@@ -41,14 +41,44 @@ Run `zig test src/image/pixel_format.zig` to test the pixel format conversion.
|
||||
Run `zig test src/image/streaming_tests.zig` to test the streaming and encoder functionality.
|
||||
Run `zig test src/image/encoder_tests.zig` to test the encoding and transcoding functionality.
|
||||
|
||||
4. JavaScript bindings:
|
||||
|
||||
Match these TypeScript signatures:
|
||||
|
||||
```ts
|
||||
namespace Bun {
|
||||
interface Image {
|
||||
readonly encoding: "jpg" | "png" | "webp" | "avif";
|
||||
|
||||
size(): Promise<{ width: number; height: number }>;
|
||||
resize(width: number, height: number, quality?: number): Image;
|
||||
resize(options: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
quality?: number;
|
||||
}): Image;
|
||||
bytes(): Promise<Buffer>;
|
||||
blob(): Promise<Blob>;
|
||||
jpg(options: { quality?: number }): Image;
|
||||
png(options: { quality?: number }): Image;
|
||||
webp(options: { quality?: number }): Image;
|
||||
avif(options: { quality?: number }): Image;
|
||||
}
|
||||
function image(bytes: Uint8Array): Image;
|
||||
}
|
||||
```
|
||||
|
||||
Use Zig's @Vector intrinsics for SIMD. Here's a couple examples:
|
||||
|
||||
```
|
||||
|
||||
/// Count the occurrences of a character in an ASCII byte array
|
||||
/// uses SIMD
|
||||
pub fn countChar(self: string, char: u8) usize {
|
||||
var total: usize = 0;
|
||||
var remaining = self;
|
||||
var total: usize = 0;
|
||||
var remaining = self;
|
||||
|
||||
const splatted: AsciiVector = @splat(char);
|
||||
|
||||
@@ -65,13 +95,14 @@ pub fn countChar(self: string, char: u8) usize {
|
||||
}
|
||||
|
||||
return total;
|
||||
|
||||
}
|
||||
|
||||
fn indexOfInterestingCharacterInStringLiteral(text_: []const u8, quote: u8) ?usize {
|
||||
var text = text_;
|
||||
const quote_: @Vector(strings.ascii_vector_size, u8) = @splat(@as(u8, quote));
|
||||
const backslash: @Vector(strings.ascii_vector_size, u8) = @splat(@as(u8, '\\'));
|
||||
const V1x16 = strings.AsciiVectorU1;
|
||||
fn indexOfInterestingCharacterInStringLiteral(text*: []const u8, quote: u8) ?usize {
|
||||
var text = text*;
|
||||
const quote\_: @Vector(strings.ascii_vector_size, u8) = @splat(@as(u8, quote));
|
||||
const backslash: @Vector(strings.ascii_vector_size, u8) = @splat(@as(u8, '\\'));
|
||||
const V1x16 = strings.AsciiVectorU1;
|
||||
|
||||
while (text.len >= strings.ascii_vector_size) {
|
||||
const vec: strings.AsciiVector = text[0..strings.ascii_vector_size].*;
|
||||
@@ -92,7 +123,9 @@ fn indexOfInterestingCharacterInStringLiteral(text_: []const u8, quote: u8) ?usi
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Some tips for working with Zig:
|
||||
@@ -219,3 +252,7 @@ Here's a complete list of Zig builtin functions:
|
||||
- @workGroupId
|
||||
- @workGroupSize
|
||||
- @workItemId
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
@@ -14,6 +14,8 @@ pub const ImageFormat = enum {
|
||||
PNG,
|
||||
WEBP,
|
||||
AVIF,
|
||||
TIFF,
|
||||
HEIC,
|
||||
|
||||
/// Get the file extension for this format
|
||||
pub fn fileExtension(self: ImageFormat) []const u8 {
|
||||
@@ -22,6 +24,8 @@ pub const ImageFormat = enum {
|
||||
.PNG => ".png",
|
||||
.WEBP => ".webp",
|
||||
.AVIF => ".avif",
|
||||
.TIFF => ".tiff",
|
||||
.HEIC => ".heic",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +36,8 @@ pub const ImageFormat = enum {
|
||||
.PNG => "image/png",
|
||||
.WEBP => "image/webp",
|
||||
.AVIF => "image/avif",
|
||||
.TIFF => "image/tiff",
|
||||
.HEIC => "image/heic",
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -241,6 +247,38 @@ pub fn encodePNG(
|
||||
return try encode(allocator, source, width, height, src_format, options);
|
||||
}
|
||||
|
||||
// Simple TIFF encoding with default options
|
||||
pub fn encodeTIFF(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
width: usize,
|
||||
height: usize,
|
||||
src_format: PixelFormat,
|
||||
) ![]u8 {
|
||||
const options = EncodingOptions{
|
||||
.format = .TIFF,
|
||||
};
|
||||
|
||||
return try encode(allocator, source, width, height, src_format, options);
|
||||
}
|
||||
|
||||
// HEIC encoding with quality setting
|
||||
pub fn encodeHEIC(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
width: usize,
|
||||
height: usize,
|
||||
src_format: PixelFormat,
|
||||
quality: u8,
|
||||
) ![]u8 {
|
||||
const options = EncodingOptions{
|
||||
.format = .HEIC,
|
||||
.quality = .{ .quality = quality },
|
||||
};
|
||||
|
||||
return try encode(allocator, source, width, height, src_format, options);
|
||||
}
|
||||
|
||||
/// Transcode image data directly from one format to another without decoding to raw pixels
|
||||
/// This is more efficient than decoding and re-encoding when converting between file formats
|
||||
pub fn transcode(
|
||||
@@ -289,4 +327,32 @@ pub fn transcodeToPNG(
|
||||
};
|
||||
|
||||
return try transcode(allocator, jpeg_data, .JPEG, .PNG, options);
|
||||
}
|
||||
|
||||
/// Transcode an image file to TIFF
|
||||
pub fn transcodeToTIFF(
|
||||
allocator: std.mem.Allocator,
|
||||
source_data: []const u8,
|
||||
source_format: ImageFormat,
|
||||
) ![]u8 {
|
||||
const options = EncodingOptions{
|
||||
.format = .TIFF,
|
||||
};
|
||||
|
||||
return try transcode(allocator, source_data, source_format, .TIFF, options);
|
||||
}
|
||||
|
||||
/// Transcode an image file to HEIC with specified quality
|
||||
pub fn transcodeToHEIC(
|
||||
allocator: std.mem.Allocator,
|
||||
source_data: []const u8,
|
||||
source_format: ImageFormat,
|
||||
quality: u8,
|
||||
) ![]u8 {
|
||||
const options = EncodingOptions{
|
||||
.format = .HEIC,
|
||||
.quality = .{ .quality = quality },
|
||||
};
|
||||
|
||||
return try transcode(allocator, source_data, source_format, .HEIC, options);
|
||||
}
|
||||
@@ -29,6 +29,8 @@ fn getUTIForFormat(format: ImageFormat) c.CFStringRef {
|
||||
.PNG => CFSTR("public.png"),
|
||||
.WEBP => CFSTR("org.webmproject.webp"), // WebP type
|
||||
.AVIF => CFSTR("public.avif"), // AVIF type
|
||||
.TIFF => CFSTR("public.tiff"), // TIFF type
|
||||
.HEIC => CFSTR("public.heic"), // HEIC type
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ fn encodeJPEG(
|
||||
libjpeg.jpeg_finish_compress(&cinfo);
|
||||
|
||||
// Copy the JPEG data to our own buffer
|
||||
var result = try allocator.alloc(u8, jpeg_buffer_size);
|
||||
const result = try allocator.alloc(u8, jpeg_buffer_size);
|
||||
@memcpy(result, jpeg_buffer[0..jpeg_buffer_size]);
|
||||
|
||||
// Clean up
|
||||
@@ -247,7 +247,7 @@ fn encodeWebP(
|
||||
}
|
||||
|
||||
// Copy to our own buffer
|
||||
var result = try allocator.alloc(u8, output_size);
|
||||
const result = try allocator.alloc(u8, output_size);
|
||||
@memcpy(result, output[0..output_size]);
|
||||
|
||||
// Free WebP's output buffer
|
||||
|
||||
@@ -362,3 +362,186 @@ test "Transcode with different quality settings" {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ fn loadSymbol(comptime T: type, name: [:0]const u8) ?T {
|
||||
if (lib_handle) |handle| {
|
||||
const symbol = std.c.dlsym(handle, name.ptr);
|
||||
if (symbol == null) return null;
|
||||
return @ptrCast(T, symbol);
|
||||
return @as(T, @ptrCast(symbol));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user