Skip to content

Commit

Permalink
Add support for JPEG in Image
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Jul 31, 2023
1 parent ccb10f5 commit 5fb65a4
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-lemons-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Add support for JPEG in `Image`
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)

LIBS := -pthread `freetype-config --libs` `aarch64-none-elf-pkg-config cairo --libs` -lquickjs -lm
LIBS := -pthread `freetype-config --libs` `aarch64-none-elf-pkg-config cairo --libs` -lturbojpeg -lquickjs -lm

#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
Expand Down
1 change: 0 additions & 1 deletion packages/runtime/src/fetch/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ function blobToStream(blob: Blob) {
}

function arrayBufferToStream(buf: ArrayBuffer) {
console.log('arrayBufferToStream', buf);
return new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array(buf));
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime/src/fetch/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,11 @@ async function fetchFile(req: Request, url: URL) {
}
const path = url.protocol === 'file:' ? `sdmc:${url.pathname}` : url.href;
const data = await Switch.readFile(path);
return new Response(data);
return new Response(data, {
headers: {
'content-length': String(data.byteLength),
},
});
}

const fetchers = new Map<string, (req: Request, url: URL) => Promise<Response>>(
Expand Down
1 change: 1 addition & 0 deletions source/canvas.c
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ static JSValue js_canvas_draw_image(JSContext *ctx, JSValueConst this_val, int a
surface = surfTemp;
}

// TODO: Support shadow
// apply shadow if there is one
// if (context->hasShadow()) {
// if(context->state->shadowBlur) {
Expand Down
99 changes: 96 additions & 3 deletions source/image.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include <png.h>
#include <turbojpeg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <cairo.h>
#include <quickjs/quickjs.h>
#include "image.h"
#include "async.h"

Expand All @@ -12,6 +12,7 @@ static JSClassID nx_image_class_id;
typedef struct
{
int err;
char *err_str;
uint8_t *input;
size_t input_size;
nx_image_t *output;
Expand All @@ -35,7 +36,14 @@ void free_image(nx_image_t *image)
if (image)
{
cairo_surface_destroy(image->surface);
free(image->buffer);
if (image->format == FORMAT_JPEG)
{
tjFree(image->buffer);
}
else
{
free(image->buffer);
}
free(image);
}
}
Expand All @@ -47,6 +55,23 @@ void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
state->ptr += length;
}

enum ImageFormat identify_image_format(uint8_t *data, size_t size)
{
if (size >= 8 && !memcmp(data, "\211PNG\r\n\032\n", 8))
{
return FORMAT_PNG;
}
else if (size >= 2 && !memcmp(data, "\377\330", 2))
{
return FORMAT_JPEG;
}
else if (size >= 12 && !memcmp(data, "RIFF", 4) && !memcmp(data + 8, "WEBP", 4))
{
return FORMAT_WEBP;
}
return FORMAT_UNKNOWN;
}

uint8_t *decode_png(uint8_t *input, size_t input_size, int *width, int *height)
{
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
Expand Down Expand Up @@ -78,11 +103,73 @@ uint8_t *decode_png(uint8_t *input, size_t input_size, int *width, int *height)
return image_data;
}

int decode_jpeg(uint8_t *jpegBuf, size_t jpegSize, uint8_t **output, int *width, int *height)
{
tjhandle handle = NULL;
int subsamp, colorspace;
int ret = -1;

handle = tjInitDecompress();
if (handle == NULL)
{
// printf("Error in tjInitDecompress(): %s\n", tjGetErrorStr());
goto cleanup;
}

if (tjDecompressHeader3(handle, jpegBuf, jpegSize, width, height, &subsamp, &colorspace) == -1)
{
// printf("Error in tjDecompressHeader3(): %s\n", tjGetErrorStr());
goto cleanup;
}

*output = tjAlloc((*width) * (*height) * tjPixelSize[TJPF_BGRA]);

if (tjDecompress2(handle, jpegBuf, jpegSize, *output, *width, 0 /*pitch*/, *height, TJPF_BGRA, TJFLAG_FASTDCT) == -1)
{
// printf("Error in tjDecompress2(): %s\n", tjGetErrorStr());
goto cleanup;
}

ret = 0;

cleanup:
if (handle != NULL)
tjDestroy(handle);

return ret;
}

void nx_decode_image_do(nx_work_t *req)
{
nx_decode_image_async_t *data = (nx_decode_image_async_t *)req->data;
nx_image_t *image = malloc(sizeof(nx_image_t));
image->buffer = decode_png(data->input, data->input_size, &data->width, &data->height);
memset(image, 0, sizeof(nx_image_t));
image->format = identify_image_format(data->input, data->input_size);
if (image->format == FORMAT_PNG)
{
image->buffer = decode_png(data->input, data->input_size, &data->width, &data->height);
}
else if (image->format == FORMAT_JPEG)
{
if (decode_jpeg(data->input, data->input_size, &image->buffer, &data->width, &data->height))
{
free(image);
data->err_str = tjGetErrorStr();
return;
}
}
else
{
free(image);
data->err_str = "Unsupported image format";
return;
}
if (image->buffer == NULL)
{
free(image);
data->err_str = "Image decode was not initialized";
return;
}
image->surface = cairo_image_surface_create_for_data(
image->buffer, CAIRO_FORMAT_ARGB32, data->width, data->height, data->width * 4);
data->output = image;
Expand All @@ -98,6 +185,12 @@ void nx_decode_image_cb(JSContext *ctx, nx_work_t *req, JSValue *args)
JS_DefinePropertyValueStr(ctx, args[0], "message", JS_NewString(ctx, strerror(data->err)), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return;
}
else if (data->err_str)
{
args[0] = JS_NewError(ctx);
JS_DefinePropertyValueStr(ctx, args[0], "message", JS_NewString(ctx, data->err_str), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return;
}

nx_image_t *image = data->output;
JSValue obj = JS_NewObject(ctx);
Expand Down
9 changes: 9 additions & 0 deletions source/image.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
#pragma once
#include "types.h"

enum ImageFormat
{
FORMAT_PNG,
FORMAT_JPEG,
FORMAT_WEBP,
FORMAT_UNKNOWN
};

typedef struct
{
enum ImageFormat format;
uint8_t *buffer;
cairo_surface_t *surface;
} nx_image_t;
Expand Down

0 comments on commit 5fb65a4

Please sign in to comment.