commit 75999fba2e796496cd072f1c11d152e31b98d548
parent a94046e17ae7f874de5e3b64686fef376aac7aa4
Author: luajitos <bbhbb2094@gmail.com>
Date: Sat, 6 Dec 2025 18:37:49 +0000
Added JPEG encoding
Diffstat:
6 files changed, 799 insertions(+), 5 deletions(-)
diff --git a/build.sh b/build.sh
@@ -179,6 +179,9 @@ ${CC} ${CFLAGS} ${LUAJIT_INCLUDE} -c decoder_PNG.c -o build/decoder_PNG.o
echo "Step 4f1: Compiling decoder_JPEG.c..."
${CC} ${CFLAGS} ${LUAJIT_INCLUDE} -c decoder_JPEG.c -o build/decoder_JPEG.o
+echo "Step 4f1b: Compiling encoder_JPEG.c..."
+${CC} ${CFLAGS} ${LUAJIT_INCLUDE} -c encoder_JPEG.c -o build/encoder_JPEG.o
+
echo "Step 4f2: Compiling splash.c..."
${CC} ${CFLAGS} ${LUAJIT_INCLUDE} -c splash.c -o build/splash.o
@@ -293,7 +296,7 @@ if [ -f "$LIBGCC_PATH" ]; then
echo "Using libgcc: $LIBGCC"
${LD} ${LDFLAGS} -T linker.ld -o build/kernel.bin \
build/boot.o build/syscall.o build/exceptions.o build/libc.o build/paging.o build/graphics.o build/vesa.o build/mouse.o build/usb_uhci.o build/keyboard.o build/ata.o build/fde.o build/diskfs.o build/partition.o build/fat16.o build/grub.o \
- build/decoder.o build/decoder_BMP.o build/decoder_PNG.o build/decoder_JPEG.o build/splash.o \
+ build/decoder.o build/decoder_BMP.o build/decoder_PNG.o build/decoder_JPEG.o build/encoder_JPEG.o build/splash.o \
build/compression.o build/deflate_impl.o build/Deflate.o build/LZMA.o build/GZip.o build/zlib.o \
build/ramdisk.o build/luajit_init.o build/kernel.o \
build/crypto_baremetal.o build/CSPRNG.o build/Ed25519.o build/X25519.o build/Curve25519.o \
@@ -311,7 +314,7 @@ else
echo "Warning: 32-bit libgcc not found, linking without it"
${LD} ${LDFLAGS} -T linker.ld -o build/kernel.bin \
build/boot.o build/syscall.o build/exceptions.o build/libc.o build/paging.o build/graphics.o build/vesa.o build/mouse.o build/usb_uhci.o build/keyboard.o build/ata.o build/fde.o build/diskfs.o build/partition.o build/fat16.o build/grub.o \
- build/decoder.o build/decoder_BMP.o build/decoder_PNG.o build/decoder_JPEG.o build/splash.o \
+ build/decoder.o build/decoder_BMP.o build/decoder_PNG.o build/decoder_JPEG.o build/encoder_JPEG.o build/splash.o \
build/compression.o build/deflate_impl.o build/Deflate.o build/LZMA.o build/GZip.o build/zlib.o \
build/ramdisk.o build/luajit_init.o build/kernel.o \
build/crypto_baremetal.o build/CSPRNG.o build/Ed25519.o build/X25519.o build/Curve25519.o \
diff --git a/encoder_JPEG.c b/encoder_JPEG.c
@@ -0,0 +1,666 @@
+/*
+ * JPEG Encoder Implementation
+ * Baseline DCT JPEG encoder (SOF0)
+ *
+ * Implements ITU-T T.81 / ISO/IEC 10918-1
+ */
+
+#include "encoder_JPEG.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+/* Zigzag order for 8x8 block - maps zigzag position to raster position
+ * zigzag[i] gives the raster index for zigzag position i
+ * Zigzag pattern: (0,0)->(0,1)->(1,0)->(2,0)->(1,1)->(0,2)->...
+ */
+static const uint8_t zigzag[64] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, /* zigzag 0-7 */
+ 17, 24, 32, 25, 18, 11, 4, 5, /* zigzag 8-15 */
+ 12, 19, 26, 33, 40, 48, 41, 34, /* zigzag 16-23 */
+ 27, 20, 13, 6, 7, 14, 21, 28, /* zigzag 24-31 */
+ 35, 42, 49, 56, 57, 50, 43, 36, /* zigzag 32-39 */
+ 29, 22, 15, 23, 30, 37, 44, 51, /* zigzag 40-47 */
+ 58, 59, 52, 45, 38, 31, 39, 46, /* zigzag 48-55 */
+ 53, 60, 61, 54, 47, 55, 62, 63 /* zigzag 56-63 */
+};
+
+/* Standard luminance quantization table */
+static const uint8_t std_lum_quant[64] = {
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77,
+ 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101,
+ 72, 92, 95, 98, 112, 100, 103, 99
+};
+
+/* Standard chrominance quantization table (ITU-T.81 Annex K Table K.2) */
+static const uint8_t std_chr_quant[64] = {
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99
+};
+
+/* DC luminance Huffman table */
+static const uint8_t dc_lum_bits[17] = {0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0};
+static const uint8_t dc_lum_val[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+/* DC chrominance Huffman table */
+static const uint8_t dc_chr_bits[17] = {0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
+static const uint8_t dc_chr_val[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+/* AC luminance Huffman table */
+static const uint8_t ac_lum_bits[17] = {0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125};
+static const uint8_t ac_lum_val[162] = {
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
+ 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa
+};
+
+/* AC chrominance Huffman table */
+static const uint8_t ac_chr_bits[17] = {0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119};
+static const uint8_t ac_chr_val[162] = {
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
+ 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
+ 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
+ 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa
+};
+
+/* Huffman code structure */
+typedef struct {
+ uint16_t code;
+ uint8_t length;
+} huff_code_t;
+
+/* Encoder state */
+typedef struct {
+ uint8_t* buffer;
+ size_t capacity;
+ size_t size;
+ uint32_t bit_buffer;
+ int bit_count;
+
+ /* Quantization tables (scaled) */
+ uint8_t lum_quant[64];
+ uint8_t chr_quant[64];
+
+ /* Huffman tables */
+ huff_code_t dc_lum_codes[12];
+ huff_code_t dc_chr_codes[12];
+ huff_code_t ac_lum_codes[256];
+ huff_code_t ac_chr_codes[256];
+} jpeg_encoder_t;
+
+/* Ensure buffer has space */
+static int ensure_capacity(jpeg_encoder_t* enc, size_t needed) {
+ if (enc->size + needed > enc->capacity) {
+ size_t new_cap = enc->capacity * 2;
+ if (new_cap < enc->size + needed) {
+ new_cap = enc->size + needed + 4096;
+ }
+ uint8_t* new_buf = (uint8_t*)realloc(enc->buffer, new_cap);
+ if (!new_buf) return 0;
+ enc->buffer = new_buf;
+ enc->capacity = new_cap;
+ }
+ return 1;
+}
+
+/* Write byte to output */
+static void write_byte(jpeg_encoder_t* enc, uint8_t b) {
+ if (!ensure_capacity(enc, 1)) return;
+ enc->buffer[enc->size++] = b;
+}
+
+/* Write 16-bit big-endian */
+static void write_word(jpeg_encoder_t* enc, uint16_t w) {
+ write_byte(enc, (w >> 8) & 0xFF);
+ write_byte(enc, w & 0xFF);
+}
+
+/* Write bits to output with byte stuffing */
+static void write_bits(jpeg_encoder_t* enc, uint16_t code, int length) {
+ enc->bit_buffer = (enc->bit_buffer << length) | code;
+ enc->bit_count += length;
+
+ while (enc->bit_count >= 8) {
+ enc->bit_count -= 8;
+ uint8_t byte = (enc->bit_buffer >> enc->bit_count) & 0xFF;
+ write_byte(enc, byte);
+ if (byte == 0xFF) {
+ write_byte(enc, 0x00); /* Byte stuffing */
+ }
+ }
+}
+
+/* Flush remaining bits */
+static void flush_bits(jpeg_encoder_t* enc) {
+ if (enc->bit_count > 0) {
+ int pad = 8 - enc->bit_count;
+ write_bits(enc, (1 << pad) - 1, pad);
+ }
+}
+
+/* Build Huffman codes from bits/values */
+static void build_huffman_codes(huff_code_t* codes, int max_codes,
+ const uint8_t* bits, const uint8_t* vals, int num_vals) {
+ memset(codes, 0, max_codes * sizeof(huff_code_t));
+
+ uint16_t code = 0;
+ int val_idx = 0;
+
+ for (int length = 1; length <= 16 && val_idx < num_vals; length++) {
+ for (int i = 0; i < bits[length] && val_idx < num_vals; i++) {
+ uint8_t val = vals[val_idx++];
+ if (val < max_codes) {
+ codes[val].code = code;
+ codes[val].length = length;
+ }
+ code++;
+ }
+ code <<= 1;
+ }
+}
+
+/* Scale quantization table by quality */
+static void scale_quant_table(uint8_t* dst, const uint8_t* src, int quality) {
+ int scale;
+ if (quality < 50) {
+ scale = 5000 / quality;
+ } else {
+ scale = 200 - quality * 2;
+ }
+
+ for (int i = 0; i < 64; i++) {
+ int val = (src[i] * scale + 50) / 100;
+ if (val < 1) val = 1;
+ if (val > 255) val = 255;
+ dst[i] = val;
+ }
+}
+
+/*
+ * Forward DCT - Independent implementation based on AA&N algorithm
+ * (Arai, Agui, and Nakajima) with correct fixed-point arithmetic
+ *
+ * This implementation uses the same constants and structure as libjpeg's jfdctint.c
+ */
+
+#define FIX_0_382683433 ((int32_t) 98) /* FIX(0.382683433) scaled by 256 */
+#define FIX_0_541196100 ((int32_t) 139) /* FIX(0.541196100) scaled by 256 */
+#define FIX_0_707106781 ((int32_t) 181) /* FIX(0.707106781) scaled by 256 */
+#define FIX_1_306562965 ((int32_t) 334) /* FIX(1.306562965) scaled by 256 */
+
+#define CONST_BITS 8
+#define PASS1_BITS 2
+
+#define DESCALE(x, n) (((x) + (1 << ((n)-1))) >> (n))
+#define MULTIPLY(var, const) ((int32_t)(var) * (const))
+
+/* Forward DCT on 8x8 block */
+static void fdct_block(int16_t* block) {
+ int32_t tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
+ int32_t tmp10, tmp11, tmp12, tmp13;
+ int32_t z1, z2, z3, z4, z5, z11, z13;
+ int32_t workspace[64];
+ int16_t *dataptr;
+ int32_t *wsptr;
+ int ctr;
+
+ /* Pass 1: process rows */
+ dataptr = block;
+ wsptr = workspace;
+ for (ctr = 0; ctr < 8; ctr++) {
+ tmp0 = dataptr[0] + dataptr[7];
+ tmp7 = dataptr[0] - dataptr[7];
+ tmp1 = dataptr[1] + dataptr[6];
+ tmp6 = dataptr[1] - dataptr[6];
+ tmp2 = dataptr[2] + dataptr[5];
+ tmp5 = dataptr[2] - dataptr[5];
+ tmp3 = dataptr[3] + dataptr[4];
+ tmp4 = dataptr[3] - dataptr[4];
+
+ /* Even part */
+ tmp10 = tmp0 + tmp3;
+ tmp13 = tmp0 - tmp3;
+ tmp11 = tmp1 + tmp2;
+ tmp12 = tmp1 - tmp2;
+
+ wsptr[0] = (tmp10 + tmp11) << PASS1_BITS;
+ wsptr[4] = (tmp10 - tmp11) << PASS1_BITS;
+
+ z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781);
+ wsptr[2] = DESCALE((tmp13 << CONST_BITS) + z1, CONST_BITS - PASS1_BITS);
+ wsptr[6] = DESCALE((tmp13 << CONST_BITS) - z1, CONST_BITS - PASS1_BITS);
+
+ /* Odd part */
+ tmp10 = tmp4 + tmp5;
+ tmp11 = tmp5 + tmp6;
+ tmp12 = tmp6 + tmp7;
+
+ z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433);
+ z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5;
+ z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5;
+ z3 = MULTIPLY(tmp11, FIX_0_707106781);
+
+ z11 = (tmp7 << CONST_BITS) + z3;
+ z13 = (tmp7 << CONST_BITS) - z3;
+
+ wsptr[5] = DESCALE(z13 + z2, CONST_BITS - PASS1_BITS);
+ wsptr[3] = DESCALE(z13 - z2, CONST_BITS - PASS1_BITS);
+ wsptr[1] = DESCALE(z11 + z4, CONST_BITS - PASS1_BITS);
+ wsptr[7] = DESCALE(z11 - z4, CONST_BITS - PASS1_BITS);
+
+ dataptr += 8;
+ wsptr += 8;
+ }
+
+ /* Pass 2: process columns */
+ wsptr = workspace;
+ dataptr = block;
+ for (ctr = 0; ctr < 8; ctr++) {
+ tmp0 = wsptr[0*8] + wsptr[7*8];
+ tmp7 = wsptr[0*8] - wsptr[7*8];
+ tmp1 = wsptr[1*8] + wsptr[6*8];
+ tmp6 = wsptr[1*8] - wsptr[6*8];
+ tmp2 = wsptr[2*8] + wsptr[5*8];
+ tmp5 = wsptr[2*8] - wsptr[5*8];
+ tmp3 = wsptr[3*8] + wsptr[4*8];
+ tmp4 = wsptr[3*8] - wsptr[4*8];
+
+ /* Even part */
+ tmp10 = tmp0 + tmp3;
+ tmp13 = tmp0 - tmp3;
+ tmp11 = tmp1 + tmp2;
+ tmp12 = tmp1 - tmp2;
+
+ dataptr[0*8] = (int16_t)DESCALE(tmp10 + tmp11, PASS1_BITS + 3);
+ dataptr[4*8] = (int16_t)DESCALE(tmp10 - tmp11, PASS1_BITS + 3);
+
+ z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781);
+ dataptr[2*8] = (int16_t)DESCALE((tmp13 << CONST_BITS) + z1, CONST_BITS + PASS1_BITS + 3);
+ dataptr[6*8] = (int16_t)DESCALE((tmp13 << CONST_BITS) - z1, CONST_BITS + PASS1_BITS + 3);
+
+ /* Odd part */
+ tmp10 = tmp4 + tmp5;
+ tmp11 = tmp5 + tmp6;
+ tmp12 = tmp6 + tmp7;
+
+ z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433);
+ z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5;
+ z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5;
+ z3 = MULTIPLY(tmp11, FIX_0_707106781);
+
+ z11 = (tmp7 << CONST_BITS) + z3;
+ z13 = (tmp7 << CONST_BITS) - z3;
+
+ dataptr[5*8] = (int16_t)DESCALE(z13 + z2, CONST_BITS + PASS1_BITS + 3);
+ dataptr[3*8] = (int16_t)DESCALE(z13 - z2, CONST_BITS + PASS1_BITS + 3);
+ dataptr[1*8] = (int16_t)DESCALE(z11 + z4, CONST_BITS + PASS1_BITS + 3);
+ dataptr[7*8] = (int16_t)DESCALE(z11 - z4, CONST_BITS + PASS1_BITS + 3);
+
+ wsptr++;
+ dataptr++;
+ }
+}
+
+/* Get bit size for value */
+static int bit_size(int val) {
+ if (val < 0) val = -val;
+ int size = 0;
+ while (val) {
+ size++;
+ val >>= 1;
+ }
+ return size;
+}
+
+/* Encode a single block */
+static int encode_block(jpeg_encoder_t* enc, int16_t* block,
+ const uint8_t* quant, int* prev_dc,
+ const huff_code_t* dc_codes, const huff_code_t* ac_codes) {
+ /* Quantize in raster order, then reorder to zigzag for encoding */
+ int16_t quant_block[64];
+ for (int i = 0; i < 64; i++) {
+ /* Quantize each DCT coefficient using the corresponding quant value */
+ int val = block[i];
+ int q = quant[i];
+ int quantized = (val >= 0) ? (val + q/2) / q : (val - q/2) / q;
+ quant_block[i] = (int16_t)quantized;
+ }
+
+ /* Now reorder to zigzag sequence for encoding */
+ int16_t zigzag_block[64];
+ for (int i = 0; i < 64; i++) {
+ zigzag_block[i] = quant_block[zigzag[i]];
+ }
+
+ /* Encode DC coefficient */
+ int dc = quant_block[0];
+ int dc_diff = dc - *prev_dc;
+ *prev_dc = dc;
+
+ int dc_size = bit_size(dc_diff);
+ if (dc_size > 11) dc_size = 11;
+
+ write_bits(enc, dc_codes[dc_size].code, dc_codes[dc_size].length);
+
+ if (dc_size > 0) {
+ int dc_val = dc_diff;
+ if (dc_diff < 0) {
+ dc_val = dc_diff + (1 << dc_size) - 1;
+ }
+ write_bits(enc, dc_val & ((1 << dc_size) - 1), dc_size);
+ }
+
+ /* Encode AC coefficients (use zigzag ordered block) */
+ int zero_count = 0;
+ for (int i = 1; i < 64; i++) {
+ int ac = zigzag_block[i];
+ if (ac == 0) {
+ zero_count++;
+ } else {
+ /* Write 16-zero runs (ZRL) */
+ while (zero_count >= 16) {
+ write_bits(enc, ac_codes[0xF0].code, ac_codes[0xF0].length);
+ zero_count -= 16;
+ }
+
+ /* Write coefficient */
+ int ac_size = bit_size(ac);
+ if (ac_size > 10) ac_size = 10;
+ int symbol = (zero_count << 4) | ac_size;
+
+ write_bits(enc, ac_codes[symbol].code, ac_codes[symbol].length);
+
+ int ac_val = ac;
+ if (ac < 0) {
+ ac_val = ac + (1 << ac_size) - 1;
+ }
+ write_bits(enc, ac_val & ((1 << ac_size) - 1), ac_size);
+
+ zero_count = 0;
+ }
+ }
+
+ /* EOB if needed */
+ if (zero_count > 0) {
+ write_bits(enc, ac_codes[0x00].code, ac_codes[0x00].length);
+ }
+
+ return 1;
+}
+
+/* Write JFIF APP0 marker */
+static void write_app0(jpeg_encoder_t* enc) {
+ write_word(enc, 0xFFE0); /* APP0 marker */
+ write_word(enc, 16); /* Length */
+ write_byte(enc, 'J');
+ write_byte(enc, 'F');
+ write_byte(enc, 'I');
+ write_byte(enc, 'F');
+ write_byte(enc, 0);
+ write_byte(enc, 1); /* Version major */
+ write_byte(enc, 1); /* Version minor */
+ write_byte(enc, 0); /* Aspect ratio units */
+ write_word(enc, 1); /* X density */
+ write_word(enc, 1); /* Y density */
+ write_byte(enc, 0); /* Thumbnail width */
+ write_byte(enc, 0); /* Thumbnail height */
+}
+
+/* Write DQT marker - table values are written in zigzag order per JPEG spec */
+static void write_dqt(jpeg_encoder_t* enc, int table_id, const uint8_t* quant) {
+ write_word(enc, 0xFFDB); /* DQT marker */
+ write_word(enc, 67); /* Length */
+ write_byte(enc, table_id); /* Table ID, 8-bit precision */
+
+ /* Write quantization values in zigzag order
+ * For zigzag position i, we write the quant value for raster position zigzag[i]
+ * This way decoder reads DQT[zz_pos] and applies it to coefficient at zz_pos */
+ for (int i = 0; i < 64; i++) {
+ write_byte(enc, quant[zigzag[i]]);
+ }
+}
+
+/* Write SOF0 marker */
+static void write_sof0(jpeg_encoder_t* enc, int width, int height) {
+ write_word(enc, 0xFFC0); /* SOF0 marker */
+ write_word(enc, 17); /* Length */
+ write_byte(enc, 8); /* Precision */
+ write_word(enc, height);
+ write_word(enc, width);
+ write_byte(enc, 3); /* Number of components */
+
+ /* Y component */
+ write_byte(enc, 1); /* ID */
+ write_byte(enc, 0x11); /* Sampling 1x1 */
+ write_byte(enc, 0); /* Quant table 0 */
+
+ /* Cb component */
+ write_byte(enc, 2);
+ write_byte(enc, 0x11);
+ write_byte(enc, 1); /* Quant table 1 */
+
+ /* Cr component */
+ write_byte(enc, 3);
+ write_byte(enc, 0x11);
+ write_byte(enc, 1);
+}
+
+/* Write DHT marker */
+static void write_dht(jpeg_encoder_t* enc, int table_class, int table_id,
+ const uint8_t* bits, const uint8_t* vals, int num_vals) {
+ int length = 19 + num_vals;
+
+ write_word(enc, 0xFFC4); /* DHT marker */
+ write_word(enc, length);
+ write_byte(enc, (table_class << 4) | table_id);
+
+ for (int i = 1; i <= 16; i++) {
+ write_byte(enc, bits[i]);
+ }
+
+ for (int i = 0; i < num_vals; i++) {
+ write_byte(enc, vals[i]);
+ }
+}
+
+/* Write SOS marker */
+static void write_sos(jpeg_encoder_t* enc) {
+ write_word(enc, 0xFFDA); /* SOS marker */
+ write_word(enc, 12); /* Length */
+ write_byte(enc, 3); /* Number of components */
+
+ write_byte(enc, 1); /* Y: DC table 0, AC table 0 */
+ write_byte(enc, 0x00);
+
+ write_byte(enc, 2); /* Cb: DC table 1, AC table 1 */
+ write_byte(enc, 0x11);
+
+ write_byte(enc, 3); /* Cr: DC table 1, AC table 1 */
+ write_byte(enc, 0x11);
+
+ write_byte(enc, 0); /* Spectral selection start */
+ write_byte(enc, 63); /* Spectral selection end */
+ write_byte(enc, 0); /* Successive approximation */
+}
+
+/* Main encode function */
+uint8_t* jpeg_encode(const uint8_t* bgra_data, int width, int height, int quality, size_t* out_size) {
+ if (!bgra_data || width <= 0 || height <= 0 || !out_size) {
+ return NULL;
+ }
+
+ if (quality < 1) quality = 1;
+ if (quality > 100) quality = 100;
+
+ jpeg_encoder_t enc;
+ memset(&enc, 0, sizeof(enc));
+
+ /* Initial buffer allocation */
+ enc.capacity = width * height + 4096;
+ enc.buffer = (uint8_t*)malloc(enc.capacity);
+ if (!enc.buffer) return NULL;
+
+ /* Scale quantization tables */
+ scale_quant_table(enc.lum_quant, std_lum_quant, quality);
+ scale_quant_table(enc.chr_quant, std_chr_quant, quality);
+
+ /* Build Huffman codes */
+ build_huffman_codes(enc.dc_lum_codes, 12, dc_lum_bits, dc_lum_val, 12);
+ build_huffman_codes(enc.dc_chr_codes, 12, dc_chr_bits, dc_chr_val, 12);
+ build_huffman_codes(enc.ac_lum_codes, 256, ac_lum_bits, ac_lum_val, 162);
+ build_huffman_codes(enc.ac_chr_codes, 256, ac_chr_bits, ac_chr_val, 162);
+
+ /* Write headers */
+ write_word(&enc, 0xFFD8); /* SOI */
+ write_app0(&enc);
+ write_dqt(&enc, 0, enc.lum_quant);
+ write_dqt(&enc, 1, enc.chr_quant);
+ write_sof0(&enc, width, height);
+ write_dht(&enc, 0, 0, dc_lum_bits, dc_lum_val, 12);
+ write_dht(&enc, 1, 0, ac_lum_bits, ac_lum_val, 162);
+ write_dht(&enc, 0, 1, dc_chr_bits, dc_chr_val, 12);
+ write_dht(&enc, 1, 1, ac_chr_bits, ac_chr_val, 162);
+ write_sos(&enc);
+
+ /* Encode blocks */
+ int prev_dc_y = 0, prev_dc_cb = 0, prev_dc_cr = 0;
+ int block_width = (width + 7) / 8;
+ int block_height = (height + 7) / 8;
+
+ int16_t block_y[64], block_cb[64], block_cr[64];
+
+ for (int by = 0; by < block_height; by++) {
+ for (int bx = 0; bx < block_width; bx++) {
+ /* Extract 8x8 block and convert BGRA to YCbCr */
+ for (int py = 0; py < 8; py++) {
+ for (int px = 0; px < 8; px++) {
+ int x = bx * 8 + px;
+ int y = by * 8 + py;
+
+ int b, g, r;
+ if (x < width && y < height) {
+ int offset = (y * width + x) * 4;
+ b = bgra_data[offset];
+ g = bgra_data[offset + 1];
+ r = bgra_data[offset + 2];
+ } else {
+ /* Pad with edge pixels */
+ int ex = (x < width) ? x : width - 1;
+ int ey = (y < height) ? y : height - 1;
+ int offset = (ey * width + ex) * 4;
+ b = bgra_data[offset];
+ g = bgra_data[offset + 1];
+ r = bgra_data[offset + 2];
+ }
+
+ /* RGB to YCbCr conversion (level shifted by -128) */
+ int idx = py * 8 + px;
+ block_y[idx] = (int16_t)(0.299 * r + 0.587 * g + 0.114 * b - 128);
+ block_cb[idx] = (int16_t)(-0.168736 * r - 0.331264 * g + 0.5 * b);
+ block_cr[idx] = (int16_t)(0.5 * r - 0.418688 * g - 0.081312 * b);
+ }
+ }
+
+ /* Apply DCT */
+ fdct_block(block_y);
+ fdct_block(block_cb);
+ fdct_block(block_cr);
+
+ /* Encode blocks */
+ encode_block(&enc, block_y, enc.lum_quant, &prev_dc_y,
+ enc.dc_lum_codes, enc.ac_lum_codes);
+ encode_block(&enc, block_cb, enc.chr_quant, &prev_dc_cb,
+ enc.dc_chr_codes, enc.ac_chr_codes);
+ encode_block(&enc, block_cr, enc.chr_quant, &prev_dc_cr,
+ enc.dc_chr_codes, enc.ac_chr_codes);
+ }
+ }
+
+ /* Flush and write EOI */
+ flush_bits(&enc);
+ write_word(&enc, 0xFFD9); /* EOI */
+
+ *out_size = enc.size;
+ return enc.buffer;
+}
+
+void jpeg_encode_free(uint8_t* data) {
+ if (data) free(data);
+}
+
+/* ============================================================================
+ * Lua Bindings
+ * ========================================================================= */
+
+/**
+ * JPEGEncode(bgra_string, width, height, quality) -> jpeg_string or nil, error
+ */
+static int l_jpeg_encode(lua_State *L) {
+ size_t data_len;
+ const char* data = luaL_checklstring(L, 1, &data_len);
+ int width = luaL_checkinteger(L, 2);
+ int height = luaL_checkinteger(L, 3);
+ int quality = luaL_optinteger(L, 4, 95);
+
+ /* Validate input */
+ size_t expected_size = (size_t)width * height * 4;
+ if (data_len < expected_size) {
+ lua_pushnil(L);
+ lua_pushstring(L, "Buffer too small for specified dimensions");
+ return 2;
+ }
+
+ if (width <= 0 || height <= 0 || width > 32768 || height > 32768) {
+ lua_pushnil(L);
+ lua_pushstring(L, "Invalid image dimensions");
+ return 2;
+ }
+
+ size_t out_size;
+ uint8_t* jpeg_data = jpeg_encode((const uint8_t*)data, width, height, quality, &out_size);
+
+ if (!jpeg_data) {
+ lua_pushnil(L);
+ lua_pushstring(L, "JPEG encoding failed");
+ return 2;
+ }
+
+ lua_pushlstring(L, (const char*)jpeg_data, out_size);
+ jpeg_encode_free(jpeg_data);
+
+ return 1;
+}
+
+int luaopen_jpegencoder(lua_State *L) {
+ lua_pushcfunction(L, l_jpeg_encode);
+ lua_setglobal(L, "JPEGEncode");
+ return 0;
+}
diff --git a/encoder_JPEG.h b/encoder_JPEG.h
@@ -0,0 +1,41 @@
+/*
+ * JPEG Encoder Header
+ * Baseline DCT JPEG encoder
+ */
+
+#ifndef ENCODER_JPEG_H
+#define ENCODER_JPEG_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Encode BGRA image data to JPEG
+ *
+ * @param bgra_data Input image in BGRA format (4 bytes per pixel)
+ * @param width Image width in pixels
+ * @param height Image height in pixels
+ * @param quality JPEG quality (1-100, higher = better quality)
+ * @param out_size Output: size of encoded JPEG data
+ * @return Pointer to encoded JPEG data (caller must free), or NULL on error
+ */
+uint8_t* jpeg_encode(const uint8_t* bgra_data, int width, int height, int quality, size_t* out_size);
+
+/**
+ * Free JPEG encoded data
+ */
+void jpeg_encode_free(uint8_t* data);
+
+/* Lua bindings */
+struct lua_State;
+int luaopen_jpegencoder(struct lua_State *L);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENCODER_JPEG_H */
diff --git a/iso_includes/apps/com.luajitos.paint/src/init.lua b/iso_includes/apps/com.luajitos.paint/src/init.lua
@@ -132,7 +132,7 @@ local buttons = {
app = app,
fs = fs,
title = "Open Image",
- filter = {"bmp", "png"}
+ filter = {"bmp", "png", "jpg", "jpeg"}
})
dlg:openDialog(function(path)
if path then
@@ -144,7 +144,7 @@ local buttons = {
{x = 95, y = 5, w = 40, h = 20, label = "Save", action = function()
local defaultName = "painting.png"
if currentFile then
- defaultName = currentFile:match("([^/]+)$") or "painting.png"
+ defaultName = currentFile:match("([^/]+)$") or defaultName
end
local dlg = Dialog.fileSave("/home", defaultName, {
app = app,
@@ -209,12 +209,37 @@ local buttons = {
-- End batch mode to finalize buffer
img:endBatch()
- -- Save based on extension
+ -- Detect format from extension, or use magic numbers from original file
local ext = path:lower():match("%.([^%.]+)$")
+
+ -- If no extension, try to detect from original file's magic bytes
+ if not ext and currentFile then
+ local origData = fs:read(currentFile)
+ if origData and #origData >= 4 then
+ local b1, b2, b3, b4 = origData:byte(1, 4)
+ if b1 == 0x42 and b2 == 0x4D then
+ ext = "bmp"
+ elseif b1 == 0x89 and b2 == 0x50 and b3 == 0x4E and b4 == 0x47 then
+ ext = "png"
+ elseif b1 == 0xFF and b2 == 0xD8 then
+ ext = "jpg"
+ end
+ end
+ end
+
+ -- Default to PNG if still unknown
+ ext = ext or "png"
+
local success, err
if ext == "bmp" then
success, err = img:saveAsBMP(path, {fs = fs})
+ elseif ext == "jpg" or ext == "jpeg" then
+ success, err = img:saveAsJPEG(path, {fs = fs})
+ elseif ext == "png" then
+ success, err = img:saveAsPNG(path, {fs = fs})
else
+ -- Unknown extension, add .png and save
+ path = path .. ".png"
success, err = img:saveAsPNG(path, {fs = fs})
end
diff --git a/iso_includes/os/libs/Image.lua b/iso_includes/os/libs/Image.lua
@@ -1066,6 +1066,57 @@ function ImageObj:_encodeBMP()
return table.concat(bmpParts)
end
+
+-- Save image as JPEG file
+-- @param path Path to save JPEG file
+-- @param options Optional table with fields: fs (SafeFS instance), quality (1-100, default 95)
+-- @return true on success, nil and error message on failure
+function ImageObj:saveAsJPEG(path, options)
+ checkPermission()
+
+ if not path then
+ error("saveAsJPEG requires a file path")
+ end
+
+ -- Parse options
+ options = options or {}
+ local quality = options.quality or 95
+ local safeFSInstance = options.fs
+
+ -- Clamp quality to valid range
+ if quality < 1 then quality = 1 end
+ if quality > 100 then quality = 100 end
+
+ -- Get BGRA buffer
+ local buffer = self:getBuffer()
+ if not buffer then
+ return nil, "Failed to get image buffer"
+ end
+
+ -- Use C JPEG encoder
+ local jpegEncode = JPEGEncode or _G.JPEGEncode
+ if not jpegEncode then
+ return nil, "JPEG encoder not available"
+ end
+
+ local jpegData, err = jpegEncode(buffer, self.width, self.height, quality)
+ if not jpegData then
+ return nil, err or "JPEG encoding failed"
+ end
+
+ -- Use provided SafeFS if specified in options
+ if safeFSInstance then
+ local success, writeErr = safeFSInstance:write(path, jpegData)
+ if not success then
+ return nil, "SafeFS write failed: " .. (writeErr or "unknown error")
+ end
+ return true
+ end
+
+ -- Otherwise use the standard filesystem (fs or CRamdisk)
+ return writeFile(path, jpegData)
+end
+
-- Internal: Load PNG file using existing decoder (converts to BGRA)
local function loadPNG(path)
-- Get functions from _G (they may be in sandbox env)
diff --git a/kernel.c b/kernel.c
@@ -51,6 +51,9 @@ extern int luaopen_fat16(lua_State *L);
/* External GRUB installation function */
extern int luaopen_grub(lua_State *L);
+/* External JPEG encoder function */
+extern int luaopen_jpegencoder(lua_State *L);
+
/* External FDE contexts array (defined in fde.c) */
extern fde_context_t fde_contexts[4];
@@ -825,6 +828,11 @@ void usermode_function(void) {
lua_setglobal(L, "grub");
terminal_writestring("GRUB module loaded!\n");
+ /* Initialize JPEG encoder module */
+ terminal_writestring("Initializing JPEG encoder...\n");
+ luaopen_jpegencoder(L);
+ terminal_writestring("JPEG encoder loaded!\n");
+
/* Register C ramdisk functions */
terminal_writestring("Registering ramdisk functions...\n");
lua_pushcfunction(L, lua_ramdisk_open);