luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 75999fba2e796496cd072f1c11d152e31b98d548
parent a94046e17ae7f874de5e3b64686fef376aac7aa4
Author: luajitos <bbhbb2094@gmail.com>
Date:   Sat,  6 Dec 2025 18:37:49 +0000

Added JPEG encoding

Diffstat:
Mbuild.sh | 7+++++--
Aencoder_JPEG.c | 666+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencoder_JPEG.h | 41+++++++++++++++++++++++++++++++++++++++++
Miso_includes/apps/com.luajitos.paint/src/init.lua | 31++++++++++++++++++++++++++++---
Miso_includes/os/libs/Image.lua | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mkernel.c | 8++++++++
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);