New image decoder

This commit is contained in:
boreddevnl 2026-03-08 19:23:22 +01:00
parent 032b154f41
commit 3fbcf3d4fd
43 changed files with 582 additions and 2754 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -17,9 +17,7 @@ ISO_DIR = iso_root
KERNEL_ELF = $(BUILD_DIR)/boredos.elf
ISO_IMAGE = boredos.iso
# Exclude old network stack files
OLD_NET_SOURCES = $(SRC_DIR)/dns.c $(SRC_DIR)/http.c $(SRC_DIR)/icmp.c $(SRC_DIR)/tcp.c
C_SOURCES = $(filter-out $(OLD_NET_SOURCES), $(wildcard $(SRC_DIR)/*.c)) \
C_SOURCES = $(wildcard $(SRC_DIR)/*.c) \
$(wildcard $(SRC_DIR)/lwip/core/*.c) \
$(wildcard $(SRC_DIR)/lwip/core/ipv4/*.c) \
$(SRC_DIR)/lwip/netif/ethernet.c \
@ -39,7 +37,6 @@ LDFLAGS = -m elf_x86_64 -nostdlib -static -pie --no-dynamic-linker \
NASMFLAGS = -f elf64
# Limine Version
LIMINE_VERSION = 10.8.2
LIMINE_URL_BASE = https://github.com/limine-bootloader/limine/raw/v$(LIMINE_VERSION)
@ -47,12 +44,10 @@ LIMINE_URL_BASE = https://github.com/limine-bootloader/limine/raw/v$(LIMINE_VERS
all: $(ISO_IMAGE)
# Ensure build directories exist
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
# Download Limine Binaries via Git
limine-setup:
@if [ ! -f limine/limine-bios.sys ]; then \
echo "Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary..."; \
@ -66,18 +61,15 @@ limine-setup:
@echo "Building Limine host utility..."; \
$(MAKE) -C limine
# Compile C Sources
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
# Assemble ASM Sources
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
# Assemble test files specifically if they get missed
$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/test_syscall.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
@ -87,20 +79,16 @@ $(BUILD_DIR)/user_test.o: $(SRC_DIR)/user_test.asm | $(BUILD_DIR)
$(BUILD_DIR)/process_asm.o: $(SRC_DIR)/process_asm.asm | $(BUILD_DIR)
$(NASM) $(NASMFLAGS) $< -o $@
# Link Kernel
$(KERNEL_ELF): $(OBJ_FILES)
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
$(MAKE) -C $(SRC_DIR)/userland
# Create ISO
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
rm -rf $(ISO_DIR)
mkdir -p $(ISO_DIR)
mkdir -p $(ISO_DIR)/EFI/BOOT
# Copy Kernel and Config
cp $(KERNEL_ELF) $(ISO_DIR)/
# Build ISO limine.conf natively with modules
cp limine.conf $(ISO_DIR)/
mkdir -p $(ISO_DIR)/bin
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
@ -111,7 +99,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
fi \
done
# Copy README and WAD
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
@if [ -f $(SRC_DIR)/userland/doom/doom1.wad ]; then \
mkdir -p $(ISO_DIR)/Library/DOOM; \
@ -119,7 +106,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
fi
# Copy Wallpapers
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
@for f in $(SRC_DIR)/images/wallpapers/*.jpg; do \
if [ -f "$$f" ]; then \
@ -130,16 +116,22 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
done
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
# Copy Limine Bootloader Files (flat structure in binary branch)
mkdir -p $(ISO_DIR)/Library/images/gif
@for f in $(SRC_DIR)/images/gif/*.gif; do \
if [ -f "$$f" ]; then \
basename=$$(basename "$$f"); \
cp "$$f" $(ISO_DIR)/Library/images/gif/; \
echo " module_path: boot():/Library/images/gif/$$basename" >> $(ISO_DIR)/limine.conf; \
fi \
done
cp limine/limine-bios.sys $(ISO_DIR)/
cp limine/limine-bios-cd.bin $(ISO_DIR)/
cp limine/limine-uefi-cd.bin $(ISO_DIR)/
# Create EFI Boot Files
cp limine/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
# Copy Fonts
mkdir -p $(ISO_DIR)/Library/Fonts
@for f in $(SRC_DIR)/fonts/*.ttf; do \
if [ -f "$$f" ]; then \
@ -149,14 +141,12 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
fi \
done
# Generate ISO
$(XORRISO) -as mkisofs -b limine-bios-cd.bin \
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
-no-emul-boot -boot-load-size 4 -boot-info-table \
--efi-boot limine-uefi-cd.bin \
-efi-boot-part --efi-boot-image --protective-msdos-label \
$(ISO_DIR) -o $(ISO_IMAGE)
# Install Limine to ISO (for BIOS boot)
./limine/limine bios-install $(ISO_IMAGE)
clean:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/.DS_Store vendored

Binary file not shown.

BIN
src/kernel/.DS_Store vendored

Binary file not shown.

View file

@ -1,137 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "net_defs.h"
#include "cmd.h"
#include "memory_manager.h"
static ipv4_address_t dns_result_ip;
static bool dns_resolved = false;
void dns_handle_response(void *data, uint16_t len) {
(void)len;
dns_header_t *dns = (dns_header_t*)data;
if ((ntohs(dns->flags) & 0x8000) == 0) return; // Not a response
// Skip queries
uint8_t *p = (uint8_t*)(dns + 1);
for(int i=0; i<ntohs(dns->q_count); i++) {
while(*p != 0) p += (*p) + 1; // Skip name
p += 5; // Skip null + type + class
}
// Parse Answers
for(int i=0; i<ntohs(dns->ans_count); i++) {
// Name (pointer or label)
if ((*p & 0xC0) == 0xC0) p += 2;
else while(*p != 0) p += (*p) + 1;
uint16_t type = ntohs(*(uint16_t*)p); p += 2;
uint16_t class = ntohs(*(uint16_t*)p); p += 2;
uint32_t ttl = ntohl(*(uint32_t*)p); p += 4;
uint16_t dlen = ntohs(*(uint16_t*)p); p += 2;
(void)class;
(void)ttl;
if (type == 1 && dlen == 4) { // A Record
dns_result_ip.bytes[0] = p[0];
dns_result_ip.bytes[1] = p[1];
dns_result_ip.bytes[2] = p[2];
dns_result_ip.bytes[3] = p[3];
dns_resolved = true;
return;
}
p += dlen;
}
}
// Callback wrapper for the network stack
static void dns_udp_callback(const ipv4_address_t* src_ip, uint16_t src_port, const mac_address_t* src_mac, const void* data, size_t length) {
(void)src_ip; (void)src_port; (void)src_mac;
dns_handle_response((void*)data, (uint16_t)length);
}
ipv4_address_t dns_resolve(const char *hostname) {
dns_resolved = false;
dns_result_ip.bytes[0] = 0;
if (!network_is_initialized()) {
cmd_write("Error: Network not initialized. Run 'netinit' first.\n");
return dns_result_ip;
}
// Register callback
extern int udp_register_callback(uint16_t port, void (*callback)(const ipv4_address_t*, uint16_t, const mac_address_t*, const void*, size_t));
udp_register_callback(5353, dns_udp_callback);
// Construct Query
uint8_t buf[512];
dns_header_t *dns = (dns_header_t*)buf;
dns->id = htons(0x1234);
dns->flags = htons(0x0100); // Standard query
dns->q_count = htons(1);
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
uint8_t *p = buf + sizeof(dns_header_t);
const char *h = hostname;
while (*h) {
const char *next = h;
while (*next && *next != '.') next++;
*p++ = (uint8_t)(next - h);
for(int i=0; i<(next-h); i++) *p++ = h[i];
h = next;
if (*h == '.') h++;
}
*p++ = 0; // End of name
*(uint16_t*)p = htons(1); p += 2; // Type A
*(uint16_t*)p = htons(1); p += 2; // Class IN
// Use DHCP provided DNS if available, otherwise fallback to Google
ipv4_address_t dns_server = get_dns_server_ip();
if (dns_server.bytes[0] == 0) {
dns_server.bytes[0] = 8; dns_server.bytes[1] = 8;
dns_server.bytes[2] = 8; dns_server.bytes[3] = 8;
}
extern int udp_send_packet(const ipv4_address_t *dest, uint16_t dest_port, uint16_t src_port, const void *data, size_t len);
// Retry loop to handle ARP resolution delay
for (int i = 0; i < 3 && !dns_resolved; i++) {
udp_send_packet(&dns_server, 53, 5353, buf, p - buf);
// Wait loop
int timeout = 20000000;
while (!dns_resolved && timeout-- > 0) {
extern void network_process_frames(void);
network_process_frames();
}
}
return dns_result_ip;
}
void cli_cmd_dns(char *args) {
if (!args || !*args) {
cmd_write("Usage: dns <hostname>\n");
return;
}
cmd_write("Resolving ");
cmd_write(args);
cmd_write("...\n");
ipv4_address_t ip = dns_resolve(args);
if (ip.bytes[0] == 0 && ip.bytes[1] == 0) {
cmd_write("Resolution failed.\n");
} else {
cmd_write("IP: ");
cmd_write_int(ip.bytes[0]); cmd_write(".");
cmd_write_int(ip.bytes[1]); cmd_write(".");
cmd_write_int(ip.bytes[2]); cmd_write(".");
cmd_write_int(ip.bytes[3]); cmd_write("\n");
}
}

View file

@ -135,6 +135,15 @@ static bool explorer_str_ends_with(const char *str, const char *suffix) {
return explorer_strcmp(str + str_len - suf_len, suffix) == 0;
}
// Helper to check if file is an image supported by stb_image
static bool explorer_is_image_file(const char *filename) {
return explorer_str_ends_with(filename, ".jpg") || explorer_str_ends_with(filename, ".JPG") ||
explorer_str_ends_with(filename, ".png") || explorer_str_ends_with(filename, ".PNG") ||
explorer_str_ends_with(filename, ".gif") || explorer_str_ends_with(filename, ".GIF") ||
explorer_str_ends_with(filename, ".bmp") || explorer_str_ends_with(filename, ".BMP") ||
explorer_str_ends_with(filename, ".tga") || explorer_str_ends_with(filename, ".TGA");
}
// Helper for label drawing (adapted from wm.c)
static void explorer_draw_icon_label(int x, int y, const char *label, uint32_t color) {
char line1[11] = {0}; // 10 chars + null
@ -815,7 +824,7 @@ static void explorer_open_target(const char *path) {
process_create_elf("A:/bin/markdown.elf", path);
} else if (explorer_str_ends_with(path, ".pnt")) {
process_create_elf("A:/bin/paint.elf", path);
} else if (explorer_str_ends_with(path, ".jpg") || explorer_str_ends_with(path, ".JPG")) {
} else if (explorer_is_image_file(path)) {
process_create_elf("A:/bin/viewer.elf", path);
} else {
process_create_elf("A:/bin/txtedit.elf", path);
@ -901,7 +910,7 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c
else draw_icon(x + 5, y + 5, "");
} else if (explorer_str_ends_with(filename, ".pnt")) {
draw_paint_icon(x + 5, y + 5, "");
} else if (explorer_str_ends_with(filename, ".jpg") || explorer_str_ends_with(filename, ".JPG")) {
} else if (explorer_is_image_file(filename)) {
// Build full path for thumbnail loading
char full_path[FAT32_MAX_PATH];
explorer_strcpy(full_path, current_path);

View file

@ -1,59 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "net_defs.h"
#include "cmd.h"
void cli_cmd_httpget(char *args) {
if (!args || !*args) {
cmd_write("Usage: httpget <hostname>\n");
return;
}
cmd_write("Resolving host...\n");
ipv4_address_t ip = dns_resolve(args);
if (ip.bytes[0] == 0 && ip.bytes[1] == 0) {
cmd_write("DNS Resolution failed.\n");
return;
}
cmd_write("Connecting to ");
cmd_write_int(ip.bytes[0]); cmd_write(".");
cmd_write_int(ip.bytes[1]); cmd_write(".");
cmd_write_int(ip.bytes[2]); cmd_write(".");
cmd_write_int(ip.bytes[3]); cmd_write("...\n");
tcp_socket_t *sock = tcp_connect(ip, 80);
if (!sock) {
cmd_write("Connection failed.\n");
return;
}
cmd_write("Sending Request...\n");
// Construct request
tcp_send(sock, "GET / HTTP/1.1\r\nHost: ", 0);
tcp_send(sock, args, 0);
tcp_send(sock, "\r\nConnection: close\r\n\r\n", 0);
cmd_write("Waiting for response...\n");
// Wait for data (simple delay loop for demo)
for(volatile int i=0; i<200000000; i++) {
extern void network_process_frames(void);
network_process_frames();
}
char buf[1024];
int len = tcp_read(sock, buf, 1023);
if (len > 0) {
buf[len] = 0;
cmd_write("\n--- Response ---\n");
cmd_write(buf);
cmd_write("\n----------------\n");
} else {
cmd_write("No data received.\n");
}
tcp_close(sock);
}

View file

@ -1,153 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "net_defs.h"
#include "cmd.h"
#include "memory_manager.h"
#include "wm.h"
static volatile bool ping_reply_received = false;
static uint16_t ping_id_counter = 0;
static uint16_t current_ping_id = 0;
static bool is_pinging = false;
void icmp_handle_packet(ipv4_address_t src, void *data, uint16_t len) {
icmp_header_t *icmp = (icmp_header_t *)data;
if (icmp->type == 0 && is_pinging && ntohs(icmp->id) == current_ping_id) { // Echo Reply
ping_reply_received = true;
// Simple output
cmd_write("Reply from ");
cmd_write_int(src.bytes[0]); cmd_write(".");
cmd_write_int(src.bytes[1]); cmd_write(".");
cmd_write_int(src.bytes[2]); cmd_write(".");
cmd_write_int(src.bytes[3]);
cmd_write(": bytes=");
cmd_write_int(len - sizeof(icmp_header_t));
cmd_write(" seq=");
cmd_write_int(ntohs(icmp->sequence));
cmd_write("\n");
}
}
void cli_cmd_ping(char *args) {
if (!args || !*args) {
cmd_write("Usage: ping <ip>\n");
return;
}
// Parse IP (Simplified)
ipv4_address_t dest;
int ip_parts[4];
const char *p = args;
for(int i=0; i<4; i++) {
ip_parts[i] = 0;
while(*p >= '0' && *p <= '9') {
ip_parts[i] = ip_parts[i]*10 + (*p - '0');
p++;
}
if(*p == '.') p++;
dest.bytes[i] = (uint8_t)ip_parts[i];
}
cmd_write("Pinging...\n");
is_pinging = true;
const int payload_size = 8;
uint8_t packet[sizeof(icmp_header_t) + payload_size];
icmp_header_t *icmp = (icmp_header_t *)packet;
for (int i = 0; i < 4; i++) {
current_ping_id = ++ping_id_counter;
icmp->type = 8; // Echo Request
icmp->code = 0;
icmp->id = htons(current_ping_id);
icmp->sequence = htons(i + 1);
icmp->checksum = 0;
// Fill payload
for (int j = 0; j < payload_size; j++) {
packet[sizeof(icmp_header_t) + j] = (uint8_t)('a' + (j % 26));
}
icmp->checksum = net_checksum(packet, sizeof(packet));
ping_reply_received = false;
ip_send_packet(dest, IP_PROTO_ICMP, packet, sizeof(packet));
uint32_t start_ticks = wm_get_ticks();
while (!ping_reply_received && (wm_get_ticks() - start_ticks) < 180) { // 3 seconds timeout
network_process_frames();
}
if (!ping_reply_received) {
cmd_write("Request timed out. (Did you run 'netinit'?)\n");
} else if (i < 3) {
// Wait a bit before next ping
uint32_t wait_start = wm_get_ticks();
while ((wm_get_ticks() - wait_start) < 60) {
network_process_frames();
}
}
}
is_pinging = false;
}
// Syscall version of ping for userland - returns success/failure
int cli_cmd_ping_syscall(ipv4_address_t *dest) {
if (!dest) return -1;
// Check if network is initialized
if (!network_is_initialized()) {
return -2; // Network not initialized
}
ipv4_address_t dest_ip = *dest;
is_pinging = true;
const int payload_size = 8;
uint8_t packet[sizeof(icmp_header_t) + payload_size];
icmp_header_t *icmp = (icmp_header_t *)packet;
int success_count = 0;
for (int i = 0; i < 4; i++) {
current_ping_id = ++ping_id_counter;
icmp->type = 8; // Echo Request
icmp->code = 0;
icmp->id = htons(current_ping_id);
icmp->sequence = htons(i + 1);
icmp->checksum = 0;
// Fill payload
for (int j = 0; j < payload_size; j++) {
packet[sizeof(icmp_header_t) + j] = (uint8_t)('a' + (j % 26));
}
icmp->checksum = net_checksum(packet, sizeof(packet));
ping_reply_received = false;
ip_send_packet(dest_ip, IP_PROTO_ICMP, packet, sizeof(packet));
uint32_t start_ticks = wm_get_ticks();
while (!ping_reply_received && (wm_get_ticks() - start_ticks) < 180) { // 3 seconds timeout
network_process_frames();
}
if (ping_reply_received) {
success_count++;
}
if (i < 3) {
// Wait a bit before next ping
uint32_t wait_start = wm_get_ticks();
while ((wm_get_ticks() - wait_start) < 60) {
network_process_frames();
}
}
}
is_pinging = false;
// Return number of successful replies
return success_count;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

View file

@ -41,6 +41,10 @@ isr%2_wrapper:
push r14
push r15
; Save SSE/FPU state
sub rsp, 512
fxsave [rsp]
; Pass current RSP as 1st argument (registers_t*)
mov rdi, rsp
@ -49,6 +53,10 @@ isr%2_wrapper:
; Update RSP with return value (task switch)
mov rsp, rax
; Restore SSE/FPU state
fxrstor [rsp]
add rsp, 512
pop r15
pop r14
pop r13
@ -146,6 +154,10 @@ exception_common:
push r14
push r15
; Save SSE/FPU state
sub rsp, 512
fxsave [rsp]
; Pass current RSP as 1st argument (registers_t*)
mov rdi, rsp
@ -154,6 +166,10 @@ exception_common:
; Switch stack if needed (for process termination)
mov rsp, rax
; Restore SSE/FPU state
fxrstor [rsp]
add rsp, 512
; Restore registers
pop r15
pop r14

View file

@ -155,6 +155,7 @@ void kmain(void) {
fat32_mkdir("/Library");
fat32_mkdir("/Library/images");
fat32_mkdir("/Library/images/Wallpapers");
fat32_mkdir("/Library/images/gif");
fat32_mkdir("/Library/Fonts");
fat32_mkdir("/Library/DOOM");

View file

@ -1,968 +0,0 @@
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
// version 1.3 (2012-03-05)
// by Martin J. Fiedler <martin.fiedler@gmx.net>
//
// This software is published under the terms of KeyJ's Research License,
// version 0.2. Usage of this software is subject to the following conditions:
// 0. There's no warranty whatsoever. The author(s) of this software can not
// be held liable for any damages that occur when using this software.
// 1. This software may be used freely for both non-commercial and commercial
// purposes.
// 2. This software may be redistributed freely as long as no fees are charged
// for the distribution and this license information is included.
// 3. This software may be modified freely except for this license information,
// which must not be changed in any way.
// 4. If anything other than configuration, indentation or comments have been
// altered in the code, the original author(s) must receive a copy of the
// modified code.
///////////////////////////////////////////////////////////////////////////////
// DOCUMENTATION SECTION //
// read this if you want to know what this is all about //
///////////////////////////////////////////////////////////////////////////////
// INTRODUCTION
// ============
//
// This is a minimal decoder for baseline JPEG images. It accepts memory dumps
// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit
// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files
// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are
// not supported. All YCbCr subsampling schemes with power-of-two ratios are
// supported, as are restart intervals. Progressive or lossless JPEG is not
// supported.
// Summed up, NanoJPEG should be able to decode all images from digital cameras
// and most common forms of other non-progressive JPEG images.
// The decoder is not optimized for speed, it's optimized for simplicity and
// small code. Image quality should be at a reasonable level. A bicubic chroma
// upsampling filter ensures that subsampled YCbCr images are rendered in
// decent quality. The decoder is not meant to deal with broken JPEG files in
// a graceful manner; if anything is wrong with the bitstream, decoding will
// simply fail.
// The code should work with every modern C compiler without problems and
// should not emit any warnings. It uses only (at least) 32-bit integer
// arithmetic and is supposed to be endianness independent and 64-bit clean.
// However, it is not thread-safe.
// COMPILE-TIME CONFIGURATION
// ==========================
//
// The following aspects of NanoJPEG can be controlled with preprocessor
// defines:
//
// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example
// program.
// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header
// file for NanoJPEG. Example:
// #define _NJ_INCLUDE_HEADER_ONLY
// #include "nanojpeg.c"
// int main(void) {
// njInit();
// // your code here
// njDone();
// }
// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy()
// functions from the standard C library (default).
// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode,
// external functions njAlloc(), njFreeMem(),
// njFillMem() and njCopyMem() need to be defined
// and implemented somewhere.
// NJ_USE_WIN32=0 = Normal mode (default).
// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and
// NJ_USE_LIBC=0, NanoJPEG will use its own
// implementations of the required C library
// functions (default if compiling with MSVC and
// NJ_USE_LIBC=0).
// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter
// (default). // 图像resize的一种算法
// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling
// (bad quality, but faster and less code).
// API
// ===
//
// For API documentation, read the "header section" below.
// EXAMPLE
// =======
//
// A few pages below, you can find an example program that uses NanoJPEG to
// convert JPEG files into PGM or PPM. To compile it, use something like
// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c
// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :)
///////////////////////////////////////////////////////////////////////////////
// HEADER SECTION //
// copy and pase this into nanojpeg.h if you want //
///////////////////////////////////////////////////////////////////////////////
#ifndef _NANOJPEG_H
#define _NANOJPEG_H
// nj_result_t: Result codes for njDecode().
typedef enum _nj_result {
NJ_OK = 0, // no error, decoding successful
NJ_NO_JPEG, // not a JPEG file
NJ_UNSUPPORTED, // unsupported format
NJ_OUT_OF_MEM, // out of memory
NJ_INTERNAL_ERR, // internal error
NJ_SYNTAX_ERROR, // syntax error
__NJ_FINISHED, // used internally, will never be reported
} nj_result_t;
// njInit: Initialize NanoJPEG.
// For safety reasons, this should be called at least one time before using
// using any of the other NanoJPEG functions.
void njInit(void);
// njDecode: Decode a JPEG image.
// Decodes a memory dump of a JPEG file into internal buffers.
// Parameters:
// jpeg = The pointer to the memory dump.
// size = The size of the JPEG file.
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
nj_result_t njDecode(const void* jpeg, const int size);
// njGetWidth: Return the width (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetWidth() is undefined.
int njGetWidth(void);
// njGetHeight: Return the height (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetHeight() is undefined.
int njGetHeight(void);
// njIsColor: Return 1 if the most recently decoded image is a color image
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
// of njGetWidth() is undefined.
int njIsColor(void);
// njGetImage: Returns the decoded image data.
// Returns a pointer to the most recently image. The memory layout it byte-
// oriented, top-down, without any padding between lines. Pixels of color
// images will be stored as three consecutive bytes for the red, green and
// blue channels. This data format is thus compatible with the PGM or PPM
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
// If njDecode() failed, the result of njGetImage() is undefined.
unsigned char* njGetImage(void);
// njGetImageSize: Returns the size (in bytes) of the image data returned
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
// undefined.
int njGetImageSize(void);
// njDone: Uninitialize NanoJPEG.
// Resets NanoJPEG's internal state and frees all memory that has been
// allocated at run-time by NanoJPEG. It is still possible to decode another
// image after a njDone() call.
void njDone(void);
#endif//_NANOJPEG_H
///////////////////////////////////////////////////////////////////////////////
// CONFIGURATION SECTION //
// adjust the default settings for the NJ_ defines here //
///////////////////////////////////////////////////////////////////////////////
#ifndef NJ_USE_LIBC
#define NJ_USE_LIBC 0
#endif
#ifndef NJ_USE_WIN32
#ifdef _MSC_VER
#define NJ_USE_WIN32 (!NJ_USE_LIBC)
#else
#define NJ_USE_WIN32 0
#endif
#endif
#ifndef NJ_CHROMA_FILTER
#define NJ_CHROMA_FILTER 1
#endif
///////////////////////////////////////////////////////////////////////////////
// EXAMPLE PROGRAM //
// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) //
///////////////////////////////////////////////////////////////////////////////
#ifdef _NJ_EXAMPLE_PROGRAM
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
int size;
char *buf;
FILE *f;
if (argc < 2) {
printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]);
return 2;
}
f = fopen(argv[1], "rb");
if (!f) {
printf("Error opening the input file.\n");
return 1;
}
fseek(f, 0, SEEK_END);
size = (int) ftell(f); // 字节
buf = malloc(size);
fseek(f, 0, SEEK_SET);
size = (int) fread(buf, 1, size, f); // 读取整个文件内容到buf
fclose(f);
njInit(); // 初始化nj_context_t
if (njDecode(buf, size)) {
printf("Error decoding the input file.\n");
return 1;
}
f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb");
if (!f) {
printf("Error opening the output file.\n");
return 1;
}
fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight());
fwrite(njGetImage(), 1, njGetImageSize(), f);
fclose(f);
njDone();
return 0;
}
#endif
// 解释什么是stride http://msdn.microsoft.com/en-us/library/windows/desktop/aa473780(v=vs.85).aspx
///////////////////////////////////////////////////////////////////////////////
// IMPLEMENTATION SECTION //
// you may stop reading here //
///////////////////////////////////////////////////////////////////////////////
#ifndef _NJ_INCLUDE_HEADER_ONLY
#include <stddef.h> // For NULL in freestanding mode
#ifdef _MSC_VER
#define NJ_INLINE static __inline
#define NJ_FORCE_INLINE static __forceinline
#else
#define NJ_INLINE static inline
#define NJ_FORCE_INLINE static inline
#endif
#if NJ_USE_LIBC
#include <stdlib.h>
#include <string.h>
#define njAllocMem malloc
#define njFreeMem free
#define njFillMem memset
#define njCopyMem memcpy
#elif NJ_USE_WIN32
#include <windows.h>
#define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size)))
#define njFreeMem(block) ((void) LocalFree((HLOCAL) block))
NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm {
mov edi, block
mov al, value
mov ecx, count
rep stosb
} }
NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm {
mov edi, dest
mov esi, src
mov ecx, count
rep movsb
} }
#else
extern void* njAllocMem(int size);
extern void njFreeMem(void* block);
extern void njFillMem(void* block, unsigned char byte, int size);
extern void njCopyMem(void* dest, const void* src, int size);
#endif
typedef struct _nj_code {
unsigned char bits, code;
} nj_vlc_code_t;
typedef struct _nj_cmp {
int cid;
int ssx, ssy; // 水平/垂直因子
int width, height;
int stride;
int qtsel; // Quantization Table量化表
int actabsel, dctabsel; // AC/DC Huffman Table
int dcpred; // DC prediction
unsigned char *pixels;
} nj_component_t; // 颜色分量
typedef struct _nj_ctx {
nj_result_t error;
const unsigned char *pos; // 待解码数据指针(按字节来)
int size; // 整个数据的长度
int length; // 某一个marker内容的长度
int width, height; // 图片宽和高度
int mbwidth, mbheight; // MCU水平/垂直个数
int mbsizex, mbsizey; // MCU宽/高
int ncomp; // 颜色分量数
nj_component_t comp[3]; // YCbCr
int qtused, qtavail; // 这两个目前看不出来很大用处
unsigned char qtab[4][64]; // 但是目前似乎只有2个
nj_vlc_code_t vlctab[4][65536]; // 构造所有16位数的Huffman基数
// 目前基本上是4个(直/交/0/1)
int buf, bufbits; // 这是用来做什么的 buf是存放内容的 bufbits是计数器存放了多少个bits
int block[64];
int rstinterval;
unsigned char *rgb; // 解析出来的RGB所要占用的内存 // 每1个点包含3个字节按找RGB的顺序
} nj_context_t;
static nj_context_t nj;
static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18,
11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35,
42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };
/*
0 1 2 3 4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
*/
NJ_FORCE_INLINE unsigned char njClip(const int x) { // 限定范围是0 ~ 255之间
return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x);
}
#define W1 2841
#define W2 2676
#define W3 2408
#define W5 1609
#define W6 1108
#define W7 565
NJ_INLINE void njRowIDCT(int* blk) { // 按行来操作的 0 ~ 7 // 8 ~ 15
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
if (!((x1 = blk[4] << 11)
| (x2 = blk[6])
| (x3 = blk[2])
| (x4 = blk[1])
| (x5 = blk[7])
| (x6 = blk[5])
| (x7 = blk[3])))
{
blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
return;
}
x0 = (blk[0] << 11) + 128;
x8 = W7 * (x4 + x5);
x4 = x8 + (W1 - W7) * x4;
x5 = x8 - (W1 + W7) * x5;
x8 = W3 * (x6 + x7);
x6 = x8 - (W3 - W5) * x6;
x7 = x8 - (W3 + W5) * x7;
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2);
x2 = x1 - (W2 + W6) * x2;
x3 = x1 + (W2 - W6) * x3;
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = (181 * (x4 + x5) + 128) >> 8;
x4 = (181 * (x4 - x5) + 128) >> 8;
blk[0] = (x7 + x1) >> 8;
blk[1] = (x3 + x2) >> 8;
blk[2] = (x0 + x4) >> 8;
blk[3] = (x8 + x6) >> 8;
blk[4] = (x8 - x6) >> 8;
blk[5] = (x0 - x4) >> 8;
blk[6] = (x3 - x2) >> 8;
blk[7] = (x7 - x1) >> 8;
}
NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) {
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
if (!((x1 = blk[8*4] << 8)
| (x2 = blk[8*6])
| (x3 = blk[8*2])
| (x4 = blk[8*1])
| (x5 = blk[8*7])
| (x6 = blk[8*5])
| (x7 = blk[8*3])))
{
x1 = njClip(((blk[0] + 32) >> 6) + 128);
for (x0 = 8; x0; --x0) {
*out = (unsigned char) x1;
out += stride;
}
return;
}
x0 = (blk[0] << 8) + 8192;
x8 = W7 * (x4 + x5) + 4;
x4 = (x8 + (W1 - W7) * x4) >> 3;
x5 = (x8 - (W1 + W7) * x5) >> 3;
x8 = W3 * (x6 + x7) + 4;
x6 = (x8 - (W3 - W5) * x6) >> 3;
x7 = (x8 - (W3 + W5) * x7) >> 3;
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2) + 4;
x2 = (x1 - (W2 + W6) * x2) >> 3;
x3 = (x1 + (W2 - W6) * x3) >> 3;
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = (181 * (x4 + x5) + 128) >> 8; // YCb和Cr的值都范围都是-128 ~ 127并且在FDCT的时候有先减去128所以现在要IDCT之后再加上128
x4 = (181 * (x4 - x5) + 128) >> 8;
*out = njClip(((x7 + x1) >> 14) + 128); out += stride;
*out = njClip(((x3 + x2) >> 14) + 128); out += stride;
*out = njClip(((x0 + x4) >> 14) + 128); out += stride;
*out = njClip(((x8 + x6) >> 14) + 128); out += stride;
*out = njClip(((x8 - x6) >> 14) + 128); out += stride;
*out = njClip(((x0 - x4) >> 14) + 128); out += stride;
*out = njClip(((x3 - x2) >> 14) + 128); out += stride;
*out = njClip(((x7 - x1) >> 14) + 128);
}
#define njThrow(e) do { nj.error = e; return; } while (0)
#define njCheckError() do { if (nj.error) return; } while (0)
static int njShowBits(int bits) { // 能放得下大于32位的值么
unsigned char newbyte;
if (!bits) return 0;
while (nj.bufbits < bits) { // 也就是说要buf的位数小于已经buf的位数的时候就直接读出来
if (nj.size <= 0) {
nj.buf = (nj.buf << 8) | 0xFF;
nj.bufbits += 8;
continue;
}
newbyte = *nj.pos++; // 数据指针是按字节
nj.size--;
nj.bufbits += 8;
nj.buf = (nj.buf << 8) | newbyte; // 高位最终会被覆盖掉比如我要buf一个64位的值怎么办
if (newbyte == 0xFF) {
if (nj.size) {
unsigned char marker = *nj.pos++;
nj.size--;
switch (marker) {
case 0x00:
case 0xFF:
break;
case 0xD9: nj.size = 0; break;
default:
if ((marker & 0xF8) != 0xD0)
nj.error = NJ_SYNTAX_ERROR;
else {
nj.buf = (nj.buf << 8) | marker;
nj.bufbits += 8;
}
}
} else
nj.error = NJ_SYNTAX_ERROR;
}
}
return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1);
}
NJ_INLINE void njSkipBits(int bits) {
if (nj.bufbits < bits)
(void) njShowBits(bits);
nj.bufbits -= bits;
}
NJ_INLINE int njGetBits(int bits) {
int res = njShowBits(bits);
njSkipBits(bits);
return res;
}
NJ_INLINE void njByteAlign(void) {
nj.bufbits &= 0xF8; // (1111 1000)8的倍数不满8的部分丢弃
}
static void njSkip(int count) {
nj.pos += count; // 数据指针增加
nj.size -= count; // 总体数据大小减去count
nj.length -= count; // 当前marker长度减去count
if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR;
}
NJ_INLINE unsigned short njDecode16(const unsigned char *pos) {
return (pos[0] << 8) | pos[1]; // 00000000 00001101
}
static void njDecodeLength(void) { // decode长度字段这个方法调用一般都是已经进入到特定的marker之后
if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR);
nj.length = njDecode16(nj.pos); // 该marker的长度(除去marker名字所占用的2个字节)
if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR);
njSkip(2);
}
NJ_INLINE void njSkipMarker(void) {
njDecodeLength();
njSkip(nj.length);
}
NJ_INLINE void njDecodeSOF(void) { // 解析Start of Frame的时候就会把所需要的内存都分配好
int i, ssxmax = 0, ssymax = 0;
nj_component_t* c;
njDecodeLength(); // 解析长度并移动数据指针
if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR);
if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); // 样本精度一般都是8
nj.height = njDecode16(nj.pos + 1); // 图片高度/宽度
nj.width = njDecode16(nj.pos + 3);
nj.ncomp = nj.pos[5]; // 颜色分量数据一般都是3
njSkip(6); // 之前共6个字节数据所以移动数据指针6个字节
switch (nj.ncomp) { // 目前只支持1和3这两种
case 1:
case 3:
break;
default:
njThrow(NJ_UNSUPPORTED);
}
if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); // 数据量肯定是要大于颜色分量数 multiply 3因为接着存颜色分量信息的每个结构占3个字节
// 颜色分量ID占用1个字节水平/垂直因子占用1个字节(高4位水平低4位垂直)量化表占用1个字节
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
c->cid = nj.pos[0]; // 颜色分量ID
if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); // 高4位(水平因子)
if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); // (00001111)低4位(垂直因子)
if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (11111101) 这里0xFC是用在这里干什么的
njSkip(3); // 移动数据指针到下一个颜色分量
nj.qtused |= 1 << c->qtsel; // 这里是做什么用的?看不出来
if (c->ssx > ssxmax) ssxmax = c->ssx; // 记录最大水平因子
if (c->ssy > ssymax) ssymax = c->ssy; // 记录最大垂直因子
}
if (nj.ncomp == 1) { // 只有一种颜色分量的时候就简单啦
c = nj.comp;
c->ssx = c->ssy = ssxmax = ssymax = 1;
}
nj.mbsizex = ssxmax << 3; // MCU宽 是 水平采样因子最大值 multiply 8
nj.mbsizey = ssymax << 3; // MCU高 是 垂直采样因子最大值 multiply 8
nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; // 分子采用+ nj.mbsizex - 1就取到大于但是最接近(等于)宽度的值,
// 并且这个值是MCU宽度整数倍 // 这里是水平方向MCU的个数
nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; // 这里是垂直方向MCU的个数
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; // 采样宽度? 最大水平/垂直因子的值就是图片原来的值,否则就会根据因子做相应的减少
c->stride = (c->width + 7) & 0x7FFFFFF8; // (0111 1111 1111 1111 1111 1111 1111 1000) 做什么以1234567结尾的都省略掉
// 变成8的整数
// 补齐8位注意前面有加7所以总是不会比原来的少比如原来是227那么这里就会变成232
// 这是按照数据单元计算的,所以不对
c->height = (nj.height * c->ssy + ssymax - 1) / ssymax;
c->stride = nj.mbwidth * nj.mbsizex * c->ssx / ssxmax; // 再计算一遍stride有什么用前面计算的是错误的没有考虑MCU宽度
// 这里都已经是round过的了所以直接计算
if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED);
if (!(c->pixels = njAllocMem(c->stride * (nj.mbheight * nj.mbsizey * c->ssy / ssymax)))) njThrow(NJ_OUT_OF_MEM); // 为分量分配内存
// 大小是所有MCU的
// 可能比图片实际
// 尺寸大
}
if (nj.ncomp == 3) { // 只有有3个颜色分量的时候才需要
nj.rgb = njAllocMem(nj.width * nj.height * nj.ncomp);
if (!nj.rgb) njThrow(NJ_OUT_OF_MEM);
}
njSkip(nj.length);
}
NJ_INLINE void njDecodeDHT(void) {
int codelen, currcnt, remain, spread, i, j;
nj_vlc_code_t *vlc;
static unsigned char counts[16]; // 码字
njDecodeLength();
while (nj.length >= 17) { // 码字的数量(16) + 类型和ID(1)
i = nj.pos[0]; // 类型和ID
if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); // (11101100)
if (i & 0x02) njThrow(NJ_UNSUPPORTED); // (00000010)
i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value
// 直流0直流1交流0交流1
for (codelen = 1; codelen <= 16; ++codelen) // 码字长度
counts[codelen - 1] = nj.pos[codelen]; // 读取码字 DHT 当中的16个字节 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00
njSkip(17);
vlc = &nj.vlctab[i][0];
remain = spread = 65536;
for (codelen = 1; codelen <= 16; ++codelen) {
spread >>= 1; // 干什么? // 65536 >> 16 = 1 每个category所包含的编码个数
currcnt = counts[codelen - 1];
if (!currcnt) continue; // 如果该位数没有码字
if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR);
remain -= currcnt << (16 - codelen); // 干什么? 计算当前size的码字占用多少VLC表的空间得到剩下的空间
if (remain < 0) njThrow(NJ_SYNTAX_ERROR);
for (i = 0; i < currcnt; ++i) { // 码字个数,同样位数的码字可以有多个
register unsigned char code = nj.pos[i]; // 有多少个就,读多少个字节
for (j = spread; j; --j) { // 保存这么多个有什么作用?
vlc->bits = (unsigned char) codelen; // 码字位数
vlc->code = code; // 码字值(这个读取出来的到底是什么00 01 02 03 04 05 06 07 08 09 0A 0B是值还是权重)
++vlc;
}
}
njSkip(currcnt);
}
while (remain--) { // 16位都填充完成剩下的就用0填(1位码字XX个2位码字XX个...)
// printf("i'm nothing vlc id %d\n", tblid);
vlc->bits = 0;
++vlc;
}
// for debug
// printf("Huffman vlc id %d\n", tblid);
// njPrintHT(tblid);
}
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}
NJ_INLINE void njDecodeDQT(void) {
int i;
unsigned char *t;
njDecodeLength();
while (nj.length >= 65) {
i = nj.pos[0]; // QT信息高4位为QT精度低4位为QT号
if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (1111 1110)这个用来检测QT号码是否正确的吗目前精度好像都为0所以这么写
nj.qtavail |= 1 << i; // XXX 直接通过这里转换为数量?
t = &nj.qtab[i][0];
for (i = 0; i < 64; ++i)
t[i] = nj.pos[i + 1]; // 读取到QT数组当中但应该还是按照文件流当中的排列
njSkip(65);
}
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}
NJ_INLINE void njDecodeDRI(void) {
njDecodeLength();
if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR);
nj.rstinterval = njDecode16(nj.pos);
njSkip(nj.length);
}
static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { // Variable Length Coding
int value = njShowBits(16); // 为什么是2个字节 这又是什么? 或许是这里的Huffman编码的码字永远是少于16位的
int bits = vlc[value].bits;
if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; }
njSkipBits(bits);
value = vlc[value].code;
if (code) *code = (unsigned char) value;
bits = value & 15; // 这个value必须是0~15之间
if (!bits) {
return 0;
}
value = njGetBits(bits); // 如果这里需要读取的值的位数超过之前njShowBits剩余的值这里会重新读取
if (value < (1 << (bits - 1)))
value += ((-1) << bits) + 1;
return value;
}
NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { // 8 x 8
unsigned char code = 0;
int value, coef = 0;
njFillMem(nj.block, 0, sizeof(nj.block));
int dcvlcval = njGetVLC(&nj.vlctab[c->dctabsel][0], NULL);
c->dcpred += dcvlcval;
nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; // DC // 这里是反量化?
do {
value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); // DC 2/3
if (!code) break; // EOB
if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); // 这是什么字段?(难道是为了兼容这个过程中可以遇到0xF0这样的数据)
coef += (code >> 4) + 1; // coefficient 系数
if (coef > 63) njThrow(NJ_SYNTAX_ERROR);
nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; // AC 这里是反量化?
} while (coef < 63);
for (coef = 0; coef < 64; coef += 8)
njRowIDCT(&nj.block[coef]); // 上面先Huffman解码/反量化,这里行(反DCT)
for (coef = 0; coef < 8; ++coef)
njColIDCT(&nj.block[coef], &out[coef], c->stride);
}
NJ_INLINE void njDecodeScan(void) {
// njPrintHT(0);
// njPrintHT(2);
// njPrintHT(1);
// njPrintHT(3);
int i, mbx, mby, sbx, sby;
int rstcount = nj.rstinterval, nextrst = 0;
nj_component_t* c;
njDecodeLength();
if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR);
if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED);
njSkip(1); // 颜色分量数量
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); // 颜色分量ID
if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR);
c->dctabsel = nj.pos[1] >> 4; // 高4位为直流表DC Table
c->actabsel = (nj.pos[1] & 1) | 2; // 低4位为交流表AC Table(这里有做特殊处理所以AC的表名不会和DC相同)
njSkip(2);
}
if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED);
njSkip(nj.length); // 忽略3个字节 通常为 00 3F 00
// 2 + 1 + 6 + 3为12字节这个marker的长度刚好为12字节
// 接下来都是编码过的图像数据
for (mbx = mby = 0;;) {
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) // 每个分量都要decode
for (sby = 0; sby < c->ssy; ++sby) // 水平/垂直因子
for (sbx = 0; sbx < c->ssx; ++sbx) {
njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]); // 读取原始编码过
// 的图片数据到block中
// 并反量化,反离散余弦变换
njCheckError();
}
if (++mbx >= nj.mbwidth) { // 读完所有的MCU到达最右就返回从下一行开始
mbx = 0;
if (++mby >= nj.mbheight) break; // 到达最底行的时候推出decode结束
}
if (nj.rstinterval && !(--rstcount)) { // restart marker
njByteAlign();
i = njGetBits(16);
if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR);
nextrst = (nextrst + 1) & 7;
rstcount = nj.rstinterval;
for (i = 0; i < 3; ++i)
nj.comp[i].dcpred = 0;
}
}
nj.error = __NJ_FINISHED;
}
#if NJ_CHROMA_FILTER
#define CF4A (-9)
#define CF4B (111)
#define CF4C (29)
#define CF4D (-3)
#define CF3A (28)
#define CF3B (109)
#define CF3C (-9)
#define CF3X (104)
#define CF3Y (27)
#define CF3Z (-3)
#define CF2A (139)
#define CF2B (-11)
#define CF(x) njClip(((x) + 64) >> 7)
// 通常我们放大图片的时候就需要upsampling缩小的时候就downsampling通称为resampling
// 这里Cb/Cr分量的会少些所以需要upsampling
NJ_INLINE void njUpsampleH(nj_component_t* c) {
const int xmax = c->width - 3;
unsigned char *out, *lin, *lout;
int x, y;
out = njAllocMem((c->width * c->height) << 1);
if (!out) njThrow(NJ_OUT_OF_MEM);
lin = c->pixels;
lout = out;
for (y = c->height; y; --y) {
lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
for (x = 0; x < xmax; ++x) {
lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]);
lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]);
}
lin += c->stride;
lout += c->width << 1;
lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
}
c->width <<= 1;
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
NJ_INLINE void njUpsampleV(nj_component_t* c) {
const int w = c->width, s1 = c->stride, s2 = s1 + s1;
unsigned char *out, *cin, *cout;
int x, y;
out = njAllocMem((c->width * c->height) << 1);
if (!out) njThrow(NJ_OUT_OF_MEM);
for (x = 0; x < w; ++x) {
cin = &c->pixels[x];
cout = &out[x];
*cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w;
*cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w;
*cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w;
cin += s1;
for (y = c->height - 3; y; --y) {
*cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w;
*cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w;
cin += s1;
}
cin += s1;
*cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w;
*cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w;
*cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
}
c->height <<= 1;
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
#else
NJ_INLINE void njUpsample(nj_component_t* c) {
int x, y, xshift = 0, yshift = 0;
unsigned char *out, *lin, *lout;
while (c->width < nj.width) { c->width <<= 1; ++xshift; }
while (c->height < nj.height) { c->height <<= 1; ++yshift; }
out = njAllocMem(c->width * c->height); // 放大后的尺寸
if (!out) njThrow(NJ_OUT_OF_MEM);
lin = c->pixels;
lout = out;
for (y = 0; y < c->height; ++y) {
lin = &c->pixels[(y >> yshift) * c->stride];
for (x = 0; x < c->width; ++x)
lout[x] = lin[x >> xshift];
lout += c->width;
}
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
#endif
NJ_INLINE void njConvert() {
int i;
nj_component_t* c;
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { // 如果需要的话就upsampling
#if NJ_CHROMA_FILTER
while ((c->width < nj.width) || (c->height < nj.height)) {
if (c->width < nj.width) njUpsampleH(c);
njCheckError();
if (c->height < nj.height) njUpsampleV(c);
njCheckError();
}
#else
if ((c->width < nj.width) || (c->height < nj.height))
njUpsample(c);
#endif
if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR);
}
if (nj.ncomp == 3) { // SEE njGetImage()
// convert to RGB
int x, yy;
unsigned char *prgb = nj.rgb;
const unsigned char *py = nj.comp[0].pixels;
const unsigned char *pcb = nj.comp[1].pixels;
const unsigned char *pcr = nj.comp[2].pixels;
// 多余的数据(编/解码是对齐用的)会被丢弃吗?
for (yy = nj.height; yy; --yy) { // 列
for (x = 0; x < nj.width; ++x) { // 行
register int y = py[x] << 8; // 这是为什么? 色彩空间转换公式计算需要
register int cb = pcb[x] - 128; // YCbCr的Cb和Cr一般都是有符号数但是在JPEG当中都是无符号数
register int cr = pcr[x] - 128;
*prgb++ = njClip((y + 359 * cr + 128) >> 8); // 色彩空间转换YCbCr到RGB
*prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8);
*prgb++ = njClip((y + 454 * cb + 128) >> 8);
}
py += nj.comp[0].stride; // 移动YCbCr数据指针每一行都是有stride的所以当需要的数据都得到时后面的就不管直接丢弃移动到下一行
pcb += nj.comp[1].stride;
pcr += nj.comp[2].stride;
}
} else if (nj.comp[0].width != nj.comp[0].stride) { // 如果宽度和stride都一样什么都不用做
// grayscale -> only remove stride
unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride];
unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width];
int y;
for (y = nj.comp[0].height - 1; y; --y) {
njCopyMem(pout, pin, nj.comp[0].width);
pin += nj.comp[0].stride;
pout += nj.comp[0].width;
}
nj.comp[0].stride = nj.comp[0].width;
}
}
void njInit(void) {
njFillMem(&nj, 0, sizeof(nj_context_t)); // 初始化nj_context_t
}
void njDone(void) {
int i;
for (i = 0; i < 3; ++i)
if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels);
if (nj.rgb) njFreeMem((void*) nj.rgb);
njInit();
}
nj_result_t njDecode(const void* jpeg, const int size) {
njDone();
nj.pos = (const unsigned char*) jpeg;
nj.size = size & 0x7FFFFFFF; //
if (nj.size < 2) return NJ_NO_JPEG;
if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; // 不以0xFFD8打头(为什么要用异或来判断?)
njSkip(2);
while (!nj.error) { // 有“错误”的时候离开
if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; // 太小或者不以0xFF打头
njSkip(2); // 移动到标签的后面(长度字段的前面)
switch (nj.pos[-1]) {
case 0xC0: njDecodeSOF(); break;
case 0xC4: njDecodeDHT(); break;
case 0xDB: njDecodeDQT(); break;
case 0xDD: njDecodeDRI(); break;
case 0xDA: njDecodeScan(); break;
case 0xFE: njSkipMarker(); break;
default:
if ((nj.pos[-1] & 0xF0) == 0xE0) // JPG0和APP0字段目前都忽略
njSkipMarker();
else
return NJ_UNSUPPORTED;
}
}
if (nj.error != __NJ_FINISHED) return nj.error;
nj.error = NJ_OK;
njConvert();
return nj.error;
}
int njGetWidth(void) { return nj.width; }
int njGetHeight(void) { return nj.height; }
int njIsColor(void) { return (nj.ncomp != 1); }
unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } // 一/三个分量
int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; }
#endif // _NJ_INCLUDE_HEADER_ONLY

View file

@ -1,10 +0,0 @@
// nanojpeg.h - Header for NanoJPEG decoder (freestanding kernel use)
#ifndef NANOJPEG_H
#define NANOJPEG_H
// Include naojpeg.c in header-only mode to get the type/function declarations
#define _NJ_INCLUDE_HEADER_ONLY
#include "nanojpeg.c"
#undef _NJ_INCLUDE_HEADER_ONLY
#endif // NANOJPEG_H

View file

@ -1,28 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "memory_manager.h"
#include <stddef.h>
void* njAllocMem(int size) {
return kmalloc((size_t)size);
}
void njFreeMem(void* block) {
if (block) kfree(block);
}
void njFillMem(void* block, unsigned char byte, int size) {
unsigned char *p = (unsigned char*)block;
for (int i = 0; i < size; i++) {
p[i] = byte;
}
}
void njCopyMem(void* dest, const void* src, int size) {
unsigned char *d = (unsigned char*)dest;
const unsigned char *s = (const unsigned char*)src;
for (int i = 0; i < size; i++) {
d[i] = s[i];
}
}

View file

@ -31,8 +31,7 @@ void process_init(void) {
kernel_proc->pml4_phys = paging_get_pml4_phys();
kernel_proc->kernel_stack = 0;
// Initialize FPU state for kernel
asm volatile("fxsave %0" : "=m"(kernel_proc->fpu_state));
// Initialize FPU/SSE state for kernel (first interrupt will capture it on stack)
kernel_proc->fpu_initialized = true;
for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL;
@ -58,12 +57,12 @@ void process_create(void* entry_point, bool is_user) {
if (!new_proc->pml4_phys) return;
// 2. Allocate aligned stack
void* stack = kmalloc_aligned(4096, 4096);
void* user_stack = kmalloc_aligned(4096, 4096);
void* kernel_stack = kmalloc_aligned(32768, 32768); // Needed for when user interrupts to Ring 0
if (is_user) {
// Map user stack to 0x800000
paging_map_page(new_proc->pml4_phys, 0x800000, v2p((uint64_t)stack), PT_PRESENT | PT_RW | PT_USER);
paging_map_page(new_proc->pml4_phys, 0x800000, v2p((uint64_t)user_stack), PT_PRESENT | PT_RW | PT_USER);
// Allocate code page aligned and copy code
void* code = kmalloc_aligned(4096, 4096);
@ -86,11 +85,16 @@ void process_create(void* entry_point, bool is_user) {
// Push 15 zeros for general purpose registers (r15 -> rax)
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
// Push 512 bytes for SSE/FPU state (fxsave_region)
// Zero it out for safety
stack_ptr = (uint64_t*)((uint64_t)stack_ptr - 512);
for (int i = 0; i < 512/8; i++) stack_ptr[i] = 0;
new_proc->kernel_stack = (uint64_t)kernel_stack + 32768;
new_proc->rsp = (uint64_t)stack_ptr;
} else {
// Kernel thread
uint64_t* stack_ptr = (uint64_t*)((uint64_t)stack + 4096);
uint64_t* stack_ptr = (uint64_t*)((uint64_t)kernel_stack + 32768);
*(--stack_ptr) = 0x10; // SS (Kernel Data)
stack_ptr--;
*stack_ptr = (uint64_t)stack_ptr; // RSP
@ -100,15 +104,21 @@ void process_create(void* entry_point, bool is_user) {
*(--stack_ptr) = 0; // int_no
*(--stack_ptr) = 0; // err_code
// Push 15 zeros for general purpose registers (r15 -> rax)
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
new_proc->kernel_stack = 0;
// Push 512 bytes for SSE/FPU state (fxsave_region)
stack_ptr = (uint64_t*)((uint64_t)stack_ptr - 512);
// Zero it out for safety
for (int i = 0; i < 512/8; i++) stack_ptr[i] = 0;
new_proc->kernel_stack = (uint64_t)kernel_stack + 32768;
new_proc->rsp = (uint64_t)stack_ptr;
kfree(user_stack); // Unused for kernel threads
}
// Initialize FPU state for new process
asm volatile("fninit");
asm volatile("fxsave %0" : "=m"(new_proc->fpu_state));
new_proc->fpu_initialized = true;
// Add to linked list
@ -246,7 +256,6 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
*(--stack_ptr) = entry_point; // RIP
*(--stack_ptr) = 0; // err_code
*(--stack_ptr) = 0; // int_no
// 15 General purpose registers
*(--stack_ptr) = 0; // RAX
*(--stack_ptr) = 0; // RBX
@ -264,6 +273,12 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
*(--stack_ptr) = 0; // R14
*(--stack_ptr) = 0; // R15
// Space for 512-byte fxsave_region
stack_ptr = (uint64_t*)((uint64_t)stack_ptr - 512);
// Initialize with a clean FPU state
asm volatile("fninit");
asm volatile("fxsave %0" : "=m"(*stack_ptr));
new_proc->kernel_stack = (uint64_t)kernel_stack + 32768;
new_proc->kernel_stack_alloc = kernel_stack;
new_proc->user_stack_alloc = stack;
@ -271,7 +286,6 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
// Initialize FPU state for new process
asm volatile("fninit");
asm volatile("fxsave %0" : "=m"(new_proc->fpu_state));
new_proc->fpu_initialized = true;
// Slot is already counted in process_count if new, or reused.
@ -296,22 +310,12 @@ uint64_t process_schedule(uint64_t current_rsp) {
// serial_write("SCHED\n");
// Save context
// Save/Restore context
current_process->rsp = current_rsp;
// Save FPU state
if (current_process->fpu_initialized) {
asm volatile("fxsave %0" : "=m"(current_process->fpu_state));
}
// Switch process
current_process = current_process->next;
// Restore FPU state
if (current_process->fpu_initialized) {
asm volatile("fxrstor %0" : : "m"(current_process->fpu_state));
}
// Update Kernel Stack for User Mode interrupts and System Calls
if (current_process->is_user && current_process->kernel_stack) {
tss_set_stack(current_process->kernel_stack);

View file

@ -15,11 +15,12 @@ struct FAT32_FileHandle;
// Registers saved on the stack by interrupts/exceptions
typedef struct registers_t {
uint8_t fxsave_region[512]; // SSE/FPU state, MUST be at the bottom (lowest address)
uint64_t r15, r14, r13, r12, r11, r10, r9, r8;
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
uint64_t int_no, err_code;
uint64_t rip, cs, rflags, rsp, ss;
} __attribute__((packed)) registers_t;
} __attribute__((packed, aligned(16))) registers_t;
typedef struct process {
uint32_t pid;
@ -45,7 +46,6 @@ typedef struct process {
struct process *next;
uint8_t fpu_state[512] __attribute__((aligned(16)));
bool fpu_initialized;
} __attribute__((aligned(16))) process_t;

29
src/kernel/stb_image.c Normal file
View file

@ -0,0 +1,29 @@
// stb_image.c wrapper for Kernel Mode
#include "memory_manager.h"
// Define custom memory allocators for stb_image in freestanding environment
#define STBI_MALLOC kmalloc
#define STBI_REALLOC krealloc
#define STBI_FREE kfree
// Kernel doesn't have standard I/O headers or functions
#define STBI_NO_STDIO
#define STBI_ASSERT(x) ((void)(x))
// Kernel doesn't have math.h, so disable float/hdr functions
#define STBI_NO_LINEAR
#define STBI_NO_HDR
// Limit maximum dimensions to prevent memory exhaustion and large stack frames
#define STBI_MAX_DIMENSIONS 4096
// Kernel doesn't have standard stdlib, disable SIMD and provide abs
#define STBI_NO_SIMD
#define STBI_NO_PSD
#define STBI_NO_PIC
#define STBI_NO_PNM
static inline int abs(int x) { return x < 0 ? -x : x; }
// Include the implementation
#define STB_IMAGE_IMPLEMENTATION
#include "userland/stb_image.h"

View file

@ -525,7 +525,18 @@ static uint64_t syscall_handler_inner(uint64_t syscall_num, uint64_t arg1, uint6
uint32_t *dest = &win->pixels[(ry + y) * win->w + rx];
uint32_t *src = &image_data[(src_y_offset + y) * (int)params[2] + src_x_offset];
for (int x = 0; x < rw; x++) {
dest[x] = src[x];
uint32_t s = src[x];
uint8_t alpha = (s >> 24) & 0xFF;
if (alpha == 0xFF) {
dest[x] = s;
} else if (alpha == 0) {
// Skip
} else {
uint32_t d = dest[x];
uint32_t rb = ((s & 0xFF00FF) * alpha + (d & 0xFF00FF) * (255 - alpha)) >> 8;
uint32_t g = ((s & 0x00FF00) * alpha + (d & 0x00FF00) * (255 - alpha)) >> 8;
dest[x] = (rb & 0xFF00FF) | (g & 0x00FF00) | 0xFF000000;
}
}
}
}

View file

@ -46,6 +46,10 @@ syscall_entry:
push r14
push r15
; Save SSE/FPU state
sub rsp, 512
fxsave [rsp]
; 4. Call C handler with registers_t*
mov rdi, rsp
sti
@ -55,6 +59,10 @@ syscall_entry:
; 5. Switch to the resulting RSP (might be different if task switched)
mov rsp, rax
; Restore SSE/FPU state
fxrstor [rsp]
add rsp, 512
; 6. Restore and return via iretq
pop r15
pop r14

View file

@ -1,207 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "net_defs.h"
#include "cmd.h"
#include "memory_manager.h"
// Simplified TCP State
typedef enum {
TCP_CLOSED,
TCP_SYN_SENT,
TCP_ESTABLISHED,
TCP_FIN_WAIT
} tcp_state_enum;
struct tcp_socket_t {
ipv4_address_t remote_ip;
uint16_t remote_port;
uint16_t local_port;
uint32_t seq_num;
uint32_t ack_num;
tcp_state_enum state;
// Receive Buffer
uint8_t *rx_buffer;
int rx_size;
int rx_pos;
bool connected;
};
static tcp_socket_t *active_socket = NULL; // Single socket support for simplicity
// Pseudo Header for Checksum
typedef struct {
uint32_t src_ip;
uint32_t dst_ip;
uint8_t reserved;
uint8_t protocol;
uint16_t tcp_len;
} __attribute__((packed)) tcp_pseudo_header_t;
static uint16_t tcp_checksum(tcp_socket_t *sock, tcp_header_t *tcp, const void *data, uint16_t len) {
uint32_t sum = 0;
// Pseudo Header
ipv4_address_t local = get_local_ip();
tcp_pseudo_header_t ph;
ph.src_ip = *(uint32_t*)local.bytes;
ph.dst_ip = *(uint32_t*)sock->remote_ip.bytes;
ph.reserved = 0;
ph.protocol = IP_PROTO_TCP;
ph.tcp_len = htons(sizeof(tcp_header_t) + len);
uint16_t *p = (uint16_t*)&ph;
for(int i=0; i<sizeof(tcp_pseudo_header_t)/2; i++) sum += p[i];
// TCP Header + Data
p = (uint16_t*)tcp;
for(int i=0; i<sizeof(tcp_header_t)/2; i++) sum += p[i];
p = (uint16_t*)data;
int dlen = len;
while(dlen > 1) {
sum += *p++;
dlen -= 2;
}
if(dlen) sum += *(uint8_t*)p;
while(sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)~sum;
}
void tcp_send_packet(tcp_socket_t *sock, uint8_t flags, const void *data, uint16_t len) {
uint16_t total_len = sizeof(tcp_header_t) + len;
uint8_t *packet = kmalloc(total_len);
tcp_header_t *tcp = (tcp_header_t*)packet;
tcp->src_port = htons(sock->local_port);
tcp->dst_port = htons(sock->remote_port);
tcp->seq_num = htonl(sock->seq_num);
tcp->ack_num = htonl(sock->ack_num);
tcp->data_offset = (sizeof(tcp_header_t) / 4) << 4;
tcp->flags = flags;
tcp->window_size = htons(8192);
tcp->urgent_ptr = 0;
tcp->checksum = 0;
if (data) {
// Copy data
uint8_t *payload = packet + sizeof(tcp_header_t);
const uint8_t *d = (const uint8_t*)data;
for(int i=0; i<len; i++) payload[i] = d[i];
}
tcp->checksum = tcp_checksum(sock, tcp, data, len);
ip_send_packet(sock->remote_ip, IP_PROTO_TCP, packet, total_len);
kfree(packet);
// Advance sequence for SYN/FIN or data
if (len > 0 || (flags & (TCP_SYN|TCP_FIN))) {
sock->seq_num += (len > 0 ? len : 1);
}
}
void tcp_handle_packet(ipv4_address_t src, void *data, uint16_t len) {
if (!active_socket) return;
tcp_header_t *tcp = (tcp_header_t*)data;
uint16_t data_len = len - ((tcp->data_offset >> 4) * 4);
uint8_t *payload = (uint8_t*)data + ((tcp->data_offset >> 4) * 4);
// Check ports
if (ntohs(tcp->dst_port) != active_socket->local_port) return;
// State Machine
if (active_socket->state == TCP_SYN_SENT) {
if ((tcp->flags & TCP_SYN) && (tcp->flags & TCP_ACK)) {
active_socket->ack_num = ntohl(tcp->seq_num) + 1;
active_socket->state = TCP_ESTABLISHED;
active_socket->connected = true;
// Send ACK
tcp_send_packet(active_socket, TCP_ACK, NULL, 0);
}
} else if (active_socket->state == TCP_ESTABLISHED) {
if (tcp->flags & TCP_FIN) {
active_socket->ack_num = ntohl(tcp->seq_num) + 1;
tcp_send_packet(active_socket, TCP_ACK | TCP_FIN, NULL, 0);
active_socket->state = TCP_CLOSED;
active_socket->connected = false;
} else if (data_len > 0) {
// Accept data
if (active_socket->rx_pos < active_socket->rx_size) {
for(int i=0; i<data_len && active_socket->rx_pos < active_socket->rx_size - 1; i++) {
active_socket->rx_buffer[active_socket->rx_pos++] = payload[i];
}
active_socket->rx_buffer[active_socket->rx_pos] = 0; // Null terminate for text
}
active_socket->ack_num = ntohl(tcp->seq_num) + data_len;
tcp_send_packet(active_socket, TCP_ACK, NULL, 0);
}
}
}
tcp_socket_t* tcp_connect(ipv4_address_t ip, uint16_t port) {
if (active_socket) kfree(active_socket);
active_socket = kmalloc(sizeof(tcp_socket_t));
active_socket->remote_ip = ip;
active_socket->remote_port = port;
active_socket->local_port = 49152 + (port % 1000); // Random-ish ephemeral
active_socket->seq_num = 1000;
active_socket->ack_num = 0;
active_socket->state = TCP_SYN_SENT;
active_socket->connected = false;
active_socket->rx_buffer = kmalloc(65536); // 64KB buffer
active_socket->rx_size = 65536;
active_socket->rx_pos = 0;
// Send SYN
tcp_send_packet(active_socket, TCP_SYN, NULL, 0);
// Wait for connection (Blocking)
int timeout = 100000000;
while (!active_socket->connected && timeout-- > 0);
if (!active_socket->connected) {
kfree(active_socket->rx_buffer);
kfree(active_socket);
active_socket = NULL;
return NULL;
}
return active_socket;
}
void tcp_send(tcp_socket_t *sock, const char *data, int len) {
if (!sock || !sock->connected) return;
if (len == 0) {
// Calculate strlen
const char *p = data;
while(*p++) len++;
}
tcp_send_packet(sock, TCP_PSH | TCP_ACK, data, len);
}
void tcp_close(tcp_socket_t *sock) {
if (!sock) return;
tcp_send_packet(sock, TCP_FIN | TCP_ACK, NULL, 0);
sock->state = TCP_CLOSED;
sock->connected = false;
// Give time for packet to go out
for(volatile int i=0; i<1000000; i++);
kfree(sock->rx_buffer);
kfree(sock);
active_socket = NULL;
}
int tcp_read(tcp_socket_t *sock, char *buffer, int max_len) {
if (!sock) return 0;
int count = 0;
for (int i = 0; i < sock->rx_pos && i < max_len; i++) {
buffer[i] = sock->rx_buffer[i];
count++;
}
return count;
}

View file

@ -13,7 +13,7 @@ BIN_DIR = bin
LIBC_SOURCES = $(wildcard libc/*.c)
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o
APP_SOURCES = $(filter-out nanojpeg.c, $(wildcard *.c))
APP_SOURCES = $(filter-out stb_image.c, $(wildcard *.c))
APP_ELFS = $(patsubst %.c, $(BIN_DIR)/%.elf, $(APP_SOURCES))
DOOM_SOURCES = $(wildcard doom/*.c)
@ -30,25 +30,25 @@ $(BIN_DIR)/crt0.o: crt0.asm
$(BIN_DIR)/%.o: libc/%.c
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/nanojpeg.o: nanojpeg.c
$(BIN_DIR)/stb_image.o: stb_image.c
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/viewer.elf: $(LIBC_OBJS) $(BIN_DIR)/viewer.o $(BIN_DIR)/nanojpeg.o
$(BIN_DIR)/viewer.elf: $(LIBC_OBJS) $(BIN_DIR)/viewer.o $(BIN_DIR)/stb_image.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/settings.elf: $(LIBC_OBJS) $(BIN_DIR)/settings.o $(BIN_DIR)/nanojpeg.o
$(BIN_DIR)/settings.elf: $(LIBC_OBJS) $(BIN_DIR)/settings.o $(BIN_DIR)/stb_image.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/browser.elf: $(LIBC_OBJS) $(BIN_DIR)/browser.o $(BIN_DIR)/nanojpeg.o
$(BIN_DIR)/browser.elf: $(LIBC_OBJS) $(BIN_DIR)/browser.o $(BIN_DIR)/stb_image.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/%.o: doom/%.c
$(CC) $(CFLAGS) -Wno-error -Idoom -c $< -o $@
$(BIN_DIR)/doom.elf: $(LIBC_OBJS) $(DOOM_OBJS) $(BIN_DIR)/nanojpeg.o
$(BIN_DIR)/doom.elf: $(LIBC_OBJS) $(DOOM_OBJS) $(BIN_DIR)/stb_image.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o

View file

@ -2,7 +2,7 @@
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
#include "libc/syscall.h"
#include "libc/libui.h"
#include "nanojpeg.h"
#include "stb_image.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -110,6 +110,13 @@ typedef struct {
float scale;
int list_depth;
int blockquote_depth; // Added blockquote support
bool img_loading;
bool img_failed;
uint32_t **img_frames;
int *img_delays;
int img_frame_count;
int img_current_frame;
uint64_t next_frame_tick;
} RenderElement;
#define MAX_ELEMENTS 65536
@ -129,18 +136,40 @@ static int focused_element = -1;
static void parse_html(const char *html);
static void parse_html_incremental(const char *html, int safe_len);
static void browser_reflow(void);
static void browser_paint(void);
static int inc_parse_offset = 0;
typedef struct {
uint32_t color;
float scale;
} FontState;
#define MAX_FONT_STACK 16
static FontState inc_font_stack[MAX_FONT_STACK];
static int inc_font_ptr = 0;
static void browser_clear(void) {
for (int i = 0; i < element_count; i++) {
if (elements[i].img_pixels) {
free(elements[i].img_pixels);
elements[i].img_pixels = NULL;
}
if (elements[i].img_frames) {
for (int k = 0; k < elements[i].img_frame_count; k++) {
if (elements[i].img_frames[k]) free(elements[i].img_frames[k]);
}
free(elements[i].img_frames);
elements[i].img_frames = NULL;
}
if (elements[i].img_delays) {
free(elements[i].img_delays);
elements[i].img_delays = NULL;
}
}
element_count = 0;
total_content_height = 0;
inc_font_ptr = 0;
}
static bool str_iequals(const char *s1, const char *s2) {
@ -275,6 +304,7 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
if (amp_pos != -1) safe_len = amp_pos;
if (safe_len > inc_parse_offset) {
parse_html_incremental(body, safe_len);
browser_reflow();
browser_paint();
ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
last_render = total;
@ -288,30 +318,71 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
return total;
}
static void decode_jpeg(unsigned char *data, int len, RenderElement *el) {
njInit();
if (njDecode(data, len) == NJ_OK) {
int img_w_orig = njGetWidth(); int img_h_orig = njGetHeight();
unsigned char *rgb = njGetImage();
if (rgb) {
static void decode_image(unsigned char *data, int len, RenderElement *el) {
int img_w_orig, img_h_orig, channels;
int frame_count = 1;
int *delays = NULL;
unsigned char *rgba = NULL;
if (len > 4 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F') {
rgba = stbi_load_gif_from_memory(data, len, &delays, &img_w_orig, &img_h_orig, &frame_count, &channels, 4);
} else {
rgba = stbi_load_from_memory(data, len, &img_w_orig, &img_h_orig, &channels, 4);
}
if (rgba && img_w_orig > 0 && img_h_orig > 0) {
int fit_w = img_w_orig; int fit_h = img_h_orig;
if (fit_w > win_w - 60) { fit_h = fit_h * (win_w - 60) / fit_w; fit_w = win_w - 60; }
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
if (frame_count > 1 && delays) {
el->img_frames = malloc(frame_count * sizeof(uint32_t *));
el->img_delays = malloc(frame_count * sizeof(int));
el->img_frame_count = frame_count;
el->img_current_frame = 0;
el->next_frame_tick = sys_system(16, 0, 0, 0, 0) + (delays[0] * 60 / 1000);
for (int i = 0; i < frame_count; i++) {
el->img_frames[i] = malloc(fit_w * fit_h * sizeof(uint32_t));
if (el->img_frames[i]) {
unsigned char *src_frame = rgba + (i * img_w_orig * img_h_orig * 4);
for (int y = 0; y < fit_h; y++) {
int sy = y * img_h_orig / fit_h;
for (int x = 0; x < fit_w; x++) {
int sx = x * img_w_orig / fit_w;
int idx = (sy * img_w_orig + sx) * 4;
uint32_t r = src_frame[idx];
uint32_t g = src_frame[idx+1];
uint32_t b = src_frame[idx+2];
uint32_t a = src_frame[idx+3];
el->img_frames[i][y * fit_w + x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
el->img_delays[i] = delays[i];
}
el->img_w = fit_w; el->img_h = fit_h;
free(delays);
} else {
el->img_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
if (el->img_pixels) {
for (int y = 0; y < fit_h; y++) {
int sy = y * img_h_orig / fit_h;
for (int x = 0; x < fit_w; x++) {
int sx = x * img_w_orig / fit_w;
int idx = (sy * img_w_orig + sx) * 3;
el->img_pixels[y * fit_w + x] = 0xFF000000 | (rgb[idx] << 16) | (rgb[idx+1] << 8) | rgb[idx+2];
int idx = (sy * img_w_orig + sx) * 4;
uint32_t r = rgba[idx];
uint32_t g = rgba[idx+1];
uint32_t b = rgba[idx+2];
uint32_t a = rgba[idx+3];
el->img_pixels[y * fit_w + x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
el->img_w = fit_w; el->img_h = fit_h;
}
}
stbi_image_free(rgba);
}
njDone();
}
static int decode_chunked_bin(char *body, int total_len) {
@ -347,6 +418,11 @@ static void load_image(RenderElement *el) {
char *u = url;
const char *s = "http://"; while(*s) *u++ = *s++;
s = current_host; while(*s) *u++ = *s++;
if (current_port != 80) {
*u++ = ':';
char pbuf[10]; itoa(current_port, pbuf);
const char* ps = pbuf; while(*ps) *u++ = *ps++;
}
if (el->attr_value[0] != '/') *u++ = '/';
s = el->attr_value; while(*s) *u++ = *s++;
*u = 0;
@ -361,8 +437,14 @@ static void load_image(RenderElement *el) {
if (strstr(img_resp, "Transfer-Encoding: chunked")) {
body_len = decode_chunked_bin(body, body_len);
}
decode_jpeg((unsigned char*)body, body_len, el);
decode_image((unsigned char*)body, body_len, el);
}
if (el->img_pixels) {
el->w = el->img_w;
el->h = el->img_h;
}
el->img_loading = false;
if (!el->img_pixels) el->img_failed = true;
}
static int line_elements[512];
@ -589,9 +671,15 @@ static void parse_html(const char *html) {
browser_clear();
list_depth = 0;
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
int i = 0; int center_depth = 0; int table_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false; uint32_t current_color = COLOR_TEXT; char current_link[256] = "";
#define EFF_CENTER ((center_depth > 0) && (table_depth == 0))
int i = 0; int center_depth = 0; int table_depth = 0; int blockquote_depth = 0; bool is_bold = false; bool is_italic = false; bool is_underline = false;
uint32_t current_color = COLOR_TEXT;
char current_link[256] = "";
float current_scale = 15.0f; float base_scale = 15.0f;
FontState font_stack[MAX_FONT_STACK];
int font_ptr = 0;
#define EFF_CENTER ((center_depth > 0) && (table_depth == 0))
bool is_space_pending = false;
char current_form_action[256] = ""; int current_form_id = 0;
bool skip_content = false;
@ -644,7 +732,16 @@ static void parse_html(const char *html) {
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div") || str_iequals(tag_name+1, "address")) emit_br();
else if (str_iequals(tag_name+1, "pre")) { emit_br(); is_pre = false; }
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) { current_color = COLOR_TEXT; current_scale = base_scale; }
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
if (font_ptr > 0) {
font_ptr--;
current_color = font_stack[font_ptr].color;
current_scale = font_stack[font_ptr].scale;
} else {
current_color = COLOR_TEXT;
current_scale = base_scale;
}
}
else if (str_iequals(tag_name+1, "head") || str_iequals(tag_name+1, "script") || str_iequals(tag_name+1, "style") || str_iequals(tag_name+1, "noscript")) skip_content = false;
else if (str_iequals(tag_name+1, "title")) {
inside_title = false;
@ -663,7 +760,7 @@ static void parse_html(const char *html) {
else if (str_iequals(tag_name, "dd")) {
emit_br();
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE;
el->content[0] = ' '; el->content[1] = ' '; el->content[2] = ' '; el->content[3] = ' '; el->content[4] = 0;
el->w = ui_get_string_width_scaled(el->content, current_scale);
@ -675,7 +772,14 @@ static void parse_html(const char *html) {
else if (str_iequals(tag_name, "b") || str_iequals(tag_name, "strong")) is_bold = true;
else if (str_iequals(tag_name, "i") || str_iequals(tag_name, "em") || str_iequals(tag_name, "cite") || str_iequals(tag_name, "var")) is_italic = true;
else if (str_iequals(tag_name, "u")) is_underline = true;
else if (str_iequals(tag_name, "tt") || str_iequals(tag_name, "code") || str_iequals(tag_name, "samp") || str_iequals(tag_name, "kbd")) { current_scale = 14.0f; }
else if (str_iequals(tag_name, "tt") || str_iequals(tag_name, "code") || str_iequals(tag_name, "samp") || str_iequals(tag_name, "kbd")) {
if (font_ptr < MAX_FONT_STACK) {
font_stack[font_ptr].color = current_color;
font_stack[font_ptr].scale = current_scale;
font_ptr++;
}
current_scale = 14.0f;
}
else if (str_iequals(tag_name, "address")) { emit_br(); }
else if (str_iequals(tag_name, "html") || str_iequals(tag_name, "body")) skip_content = false;
else if (str_iequals(tag_name, "head")) skip_content = true;
@ -690,6 +794,11 @@ static void parse_html(const char *html) {
current_scale = base_scale;
}
else if (str_iequals(tag_name, "font")) {
if (font_ptr < MAX_FONT_STACK) {
font_stack[font_ptr].color = current_color;
font_stack[font_ptr].scale = current_scale;
font_ptr++;
}
char *color_str = str_istrstr(attr_buf, "color=\"");
if (color_str) {
current_color = parse_html_color(color_str + 7);
@ -736,7 +845,7 @@ static void parse_html(const char *html) {
else if (str_iequals(tag_name, "li")) {
emit_br();
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE;
if (list_depth > 0 && list_type[list_depth - 1] == 1) { // OL
@ -782,7 +891,7 @@ static void parse_html(const char *html) {
} else if (str_iequals(tag_name, "hr")) {
emit_br();
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_HR;
el->list_depth = list_depth;
el->blockquote_depth = blockquote_depth; // Set blockquote depth
@ -792,19 +901,19 @@ static void parse_html(const char *html) {
emit_br();
} else if (str_iequals(tag_name, "img")) {
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
char *src = str_istrstr(attr_buf, "src=\"");
if (src) {
src += 5; int l = 0;
while(src[l] && src[l] != '\"' && l < 255) { el->attr_value[l] = src[l]; l++; }
el->attr_value[l] = 0; load_image(el);
el->attr_value[l] = 0; el->img_loading = true; // Deferred load
}
if (el->img_pixels) { el->w = el->img_w; el->h = el->img_h; }
el->blockquote_depth = blockquote_depth; // Set blockquote depth
} else if (str_iequals(tag_name, "input")) {
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
char *val = str_istrstr(attr_buf, "value=\"");
char *ph = str_istrstr(attr_buf, "placeholder=\"");
@ -858,7 +967,7 @@ static void parse_html(const char *html) {
} else {
int word_w = ui_get_string_width_scaled(word, current_scale);
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
int k=0; while(word[k]) { el->content[k] = word[k]; k++; } el->content[k] = 0;
el->w = word_w; el->h = ui_get_font_height_scaled(current_scale);
el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color;
@ -884,7 +993,7 @@ static void parse_html(const char *html) {
} else {
int word_w = ui_get_string_width_scaled(word, current_scale);
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
int k=0; while(word[k]) { el->content[k] = word[k]; k++; } el->content[k] = 0;
el->w = word_w; el->h = ui_get_font_height_scaled(current_scale);
el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color;
@ -902,7 +1011,7 @@ static void parse_html(const char *html) {
is_space_pending = false;
if (element_count < MAX_ELEMENTS && !inside_title) {
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE; el->content[0] = ' '; el->content[1] = 0;
el->w = ui_get_string_width_scaled(" ", current_scale);
el->h = ui_get_font_height_scaled(current_scale);
@ -926,7 +1035,7 @@ static void parse_html(const char *html) {
if (element_count < MAX_ELEMENTS) {
int word_w = ui_get_string_width_scaled(word, current_scale);
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
memset(el, 0, sizeof(RenderElement));
int k=0; while(word[k]) { el->content[k] = word[k]; k++; } el->content[k] = 0;
el->w = word_w; el->h = ui_get_font_height_scaled(current_scale);
el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color;
@ -1025,7 +1134,16 @@ static void parse_html_incremental(const char *html, int safe_len) {
else if (str_iequals(tag_name+1, "a")) current_link[0] = 0;
else if (str_iequals(tag_name+1, "p") || str_iequals(tag_name+1, "li") || str_iequals(tag_name+1, "div")) emit_br();
else if (str_iequals(tag_name+1, "pre")) { emit_br(); is_pre = false; }
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) { current_color = COLOR_TEXT; current_scale = base_scale; }
else if (str_iequals(tag_name+1, "font") || str_iequals(tag_name+1, "tt") || str_iequals(tag_name+1, "code") || str_iequals(tag_name+1, "samp") || str_iequals(tag_name+1, "kbd")) {
if (inc_font_ptr > 0) {
inc_font_ptr--;
current_color = inc_font_stack[inc_font_ptr].color;
current_scale = inc_font_stack[inc_font_ptr].scale;
} else {
current_color = COLOR_TEXT;
current_scale = base_scale;
}
}
else if (str_iequals(tag_name+1, "head") || str_iequals(tag_name+1, "script") || str_iequals(tag_name+1, "style") || str_iequals(tag_name+1, "noscript")) skip_content = false;
else if (str_iequals(tag_name+1, "title")) {
inside_title = false;
@ -1041,7 +1159,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
else if (str_iequals(tag_name, "dl")) { emit_br(); list_type[list_depth] = 2; list_depth++; }
else if (str_iequals(tag_name, "dt")) { emit_br(); is_bold = true; }
else if (str_iequals(tag_name, "dd")) {
emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
emit_br(); RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE; el->content[0] = ' '; el->content[1] = ' '; el->content[2] = ' '; el->content[3] = ' '; el->content[4] = 0;
el->w = ui_get_string_width_scaled(el->content, current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->italic = is_italic; el->underline = is_underline; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
}
@ -1049,13 +1167,25 @@ static void parse_html_incremental(const char *html, int safe_len) {
else if (str_iequals(tag_name, "i") || str_iequals(tag_name, "em") || str_iequals(tag_name, "cite") || str_iequals(tag_name, "var")) is_italic = true;
else if (str_iequals(tag_name, "u")) is_underline = true;
else if (str_iequals(tag_name, "address")) emit_br();
else if (str_iequals(tag_name, "tt") || str_iequals(tag_name, "code") || str_iequals(tag_name, "samp") || str_iequals(tag_name, "kbd")) current_scale = 14.0f;
else if (str_iequals(tag_name, "tt") || str_iequals(tag_name, "code") || str_iequals(tag_name, "samp") || str_iequals(tag_name, "kbd")) {
if (inc_font_ptr < MAX_FONT_STACK) {
inc_font_stack[inc_font_ptr].color = current_color;
inc_font_stack[inc_font_ptr].scale = current_scale;
inc_font_ptr++;
}
current_scale = 14.0f;
}
else if (tag_name[0] == 'h' && tag_name[1] >= '1' && tag_name[1] <= '6') {
emit_br(); emit_br(); is_bold = true;
if (tag_name[1] == '1') base_scale = 32.0f; else if (tag_name[1] == '2') base_scale = 24.0f; else if (tag_name[1] == '3') base_scale = 20.0f; else base_scale = 18.0f;
current_scale = base_scale;
}
else if (str_iequals(tag_name, "font")) {
if (inc_font_ptr < MAX_FONT_STACK) {
inc_font_stack[inc_font_ptr].color = current_color;
inc_font_stack[inc_font_ptr].scale = current_scale;
inc_font_ptr++;
}
char *color_str = str_istrstr(attr_buf, "color=\"");
if (color_str) current_color = parse_html_color(color_str + 7); else { color_str = str_istrstr(attr_buf, "color="); if (color_str) current_color = parse_html_color(color_str + 6); }
char *size_str = str_istrstr(attr_buf, "size=\""); int offset = 0; if (size_str) offset = 6; else { size_str = str_istrstr(attr_buf, "size="); if (size_str) offset = 5; }
@ -1070,7 +1200,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
else if (str_iequals(tag_name, "p") || str_iequals(tag_name, "div")) emit_br();
else if (str_iequals(tag_name, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
else if (str_iequals(tag_name, "li")) {
emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
emit_br(); RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE; if (list_depth > 0 && list_type[list_depth - 1] == 1) { char num[16]; itoa(list_index[list_depth - 1]++, num); int l=0; while(num[l]) { el->content[l] = num[l]; l++; } el->content[l++] = '.'; el->content[l++] = ' '; el->content[l] = 0; }
else if (list_depth > 0 && list_type[list_depth - 1] == 2) { el->content[0] = ' '; el->content[1] = 0; } else { el->content[0] = '-'; el->content[1] = ' '; el->content[2] = 0; }
el->w = ui_get_string_width_scaled(el->content, current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
@ -1083,8 +1213,8 @@ static void parse_html_incremental(const char *html, int safe_len) {
else if (str_iequals(tag_name, "hr")) { emit_br(); RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_HR; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth; el->h = 10; el->centered = true; emit_br(); }
else if (str_iequals(tag_name, "img")) {
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
char *src = str_istrstr(attr_buf, "src=\""); if (src) { src += 5; int l = 0; while(src[l] && src[l] != '"' && l < 255) { el->attr_value[l] = src[l]; l++; } el->attr_value[l] = 0; load_image(el); }
if (el->img_pixels) { el->w = el->img_w; el->h = el->img_h; } el->blockquote_depth = blockquote_depth;
char *src = str_istrstr(attr_buf, "src=\""); if (src) { src += 5; int l = 0; while(src[l] && src[l] != '"' && l < 255) { el->attr_value[l] = src[l]; l++; } el->attr_value[l] = 0; el->img_loading = true; }
el->blockquote_depth = blockquote_depth;
}
else if (str_iequals(tag_name, "input")) {
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0; el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
@ -1107,7 +1237,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
if (html[i] == '\n' || html[i] == '\r') {
if (w_idx > 0) {
word[w_idx] = 0; decode_html_entities(word); int word_w = ui_get_string_width_scaled(word, current_scale);
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
int k=0; while(word[k]) { el->content[k] = word[k]; k++; } el->content[k] = 0;
el->w = word_w; el->h = ui_get_font_height_scaled(current_scale); el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->italic = is_italic; el->underline = is_underline; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
if (current_link[0]) { int k=0; while(current_link[k]) { el->link_url[k] = current_link[k]; k++; } el->link_url[k] = 0; }
@ -1118,7 +1248,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
}
if (w_idx > 0) {
word[w_idx] = 0; decode_html_entities(word); int word_w = ui_get_string_width_scaled(word, current_scale);
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
int k=0; while(word[k]) { el->content[k] = word[k]; k++; } el->content[k] = 0;
el->w = word_w; el->h = ui_get_font_height_scaled(current_scale); el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->italic = is_italic; el->underline = is_underline; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
if (current_link[0]) { int k=0; while(current_link[k]) { el->link_url[k] = current_link[k]; k++; } el->link_url[k] = 0; }
@ -1130,7 +1260,7 @@ static void parse_html_incremental(const char *html, int safe_len) {
if (is_space_pending) {
is_space_pending = false;
if (element_count < MAX_ELEMENTS) {
RenderElement *el = &elements[element_count++]; for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
RenderElement *el = &elements[element_count++]; memset(el, 0, sizeof(RenderElement));
el->tag = TAG_NONE; el->content[0] = ' '; el->content[1] = 0; el->w = ui_get_string_width_scaled(" ", current_scale); el->h = ui_get_font_height_scaled(current_scale); el->color = current_color; el->centered = EFF_CENTER; el->bold = is_bold; el->italic = is_italic; el->underline = is_underline; el->scale = current_scale; el->list_depth = list_depth; el->blockquote_depth = blockquote_depth;
}
}
@ -1186,7 +1316,9 @@ static void browser_paint(void) {
int draw_y = el->y - scroll_y + URL_BAR_H;
if (draw_y < URL_BAR_H - 400 || draw_y > win_h) continue;
if (el->tag == TAG_IMG) {
if (el->img_pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, el->img_pixels);
uint32_t *pixels = el->img_pixels;
if (el->img_frames) pixels = el->img_frames[el->img_current_frame];
if (pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, pixels);
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
} else if (el->tag == TAG_INPUT) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF);
@ -1228,7 +1360,9 @@ static void browser_paint(void) {
ui_draw_rect(win_browser, el->x, draw_y + el->h / 2, el->w, 2, 0xFF888888);
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
} else if (el->tag == TAG_NONE) {
if (el->content[0] != ' ' || el->content[1] != 0) {
ui_draw_string_scaled(win_browser, el->x, draw_y, el->content, el->color, el->scale);
}
if (el->bold) {
ui_draw_string_scaled(win_browser, el->x + 1, draw_y, el->content, el->color, el->scale);
}
@ -1557,6 +1691,37 @@ int main(int argc, char **argv) {
browser_reflow(); browser_paint(); ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
needs_repaint = false;
}
// Background image loading
bool loaded_any = false;
for (int i = 0; i < element_count; i++) {
if (elements[i].tag == TAG_IMG && elements[i].img_loading && !elements[i].img_pixels && !elements[i].img_failed) {
load_image(&elements[i]);
loaded_any = true;
break; // Load one at a time to stay responsive
}
}
if (loaded_any) {
browser_reflow(); browser_paint(); ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
}
// Animated GIF progress
bool gif_updated = false;
long long now = sys_system(16, 0, 0, 0, 0);
for (int i = 0; i < element_count; i++) {
if (elements[i].tag == TAG_IMG && elements[i].img_frames && elements[i].img_frame_count > 1) {
if (now >= elements[i].next_frame_tick) {
elements[i].img_current_frame = (elements[i].img_current_frame + 1) % elements[i].img_frame_count;
elements[i].next_frame_tick = now + (elements[i].img_delays[elements[i].img_current_frame] * 60 / 1000);
if (elements[i].next_frame_tick <= now) elements[i].next_frame_tick = now + 1;
gif_updated = true;
}
}
}
if (gif_updated) {
browser_paint(); ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
}
for(volatile int x=0; x<10000; x++);
}
return 0;

View file

@ -0,0 +1,6 @@
#ifndef BOREDOS_LIBC_STRING_H
#define BOREDOS_LIBC_STRING_H
#include <stdlib.h>
#endif

View file

@ -1,967 +0,0 @@
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
// version 1.3 (2012-03-05)
// by Martin J. Fiedler <martin.fiedler@gmx.net>
//
// This software is published under the terms of KeyJ's Research License,
// version 0.2. Usage of this software is subject to the following conditions:
// 0. There's no warranty whatsoever. The author(s) of this software can not
// be held liable for any damages that occur when using this software.
// 1. This software may be used freely for both non-commercial and commercial
// purposes.
// 2. This software may be redistributed freely as long as no fees are charged
// for the distribution and this license information is included.
// 3. This software may be modified freely except for this license information,
// which must not be changed in any way.
// 4. If anything other than configuration, indentation or comments have been
// altered in the code, the original author(s) must receive a copy of the
// modified code.
///////////////////////////////////////////////////////////////////////////////
// DOCUMENTATION SECTION //
// read this if you want to know what this is all about //
///////////////////////////////////////////////////////////////////////////////
// INTRODUCTION
// ============
//
// This is a minimal decoder for baseline JPEG images. It accepts memory dumps
// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit
// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files
// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are
// not supported. All YCbCr subsampling schemes with power-of-two ratios are
// supported, as are restart intervals. Progressive or lossless JPEG is not
// supported.
// Summed up, NanoJPEG should be able to decode all images from digital cameras
// and most common forms of other non-progressive JPEG images.
// The decoder is not optimized for speed, it's optimized for simplicity and
// small code. Image quality should be at a reasonable level. A bicubic chroma
// upsampling filter ensures that subsampled YCbCr images are rendered in
// decent quality. The decoder is not meant to deal with broken JPEG files in
// a graceful manner; if anything is wrong with the bitstream, decoding will
// simply fail.
// The code should work with every modern C compiler without problems and
// should not emit any warnings. It uses only (at least) 32-bit integer
// arithmetic and is supposed to be endianness independent and 64-bit clean.
// However, it is not thread-safe.
// COMPILE-TIME CONFIGURATION
// ==========================
//
// The following aspects of NanoJPEG can be controlled with preprocessor
// defines:
//
// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example
// program.
// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header
// file for NanoJPEG. Example:
// #define _NJ_INCLUDE_HEADER_ONLY
// #include "nanojpeg.c"
// int main(void) {
// njInit();
// // your code here
// njDone();
// }
// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy()
// functions from the standard C library (default).
// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode,
// external functions njAlloc(), njFreeMem(),
// njFillMem() and njCopyMem() need to be defined
// and implemented somewhere.
// NJ_USE_WIN32=0 = Normal mode (default).
// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and
// NJ_USE_LIBC=0, NanoJPEG will use its own
// implementations of the required C library
// functions (default if compiling with MSVC and
// NJ_USE_LIBC=0).
// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter
// (default). // 图像resize的一种算法
// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling
// (bad quality, but faster and less code).
// API
// ===
//
// For API documentation, read the "header section" below.
// EXAMPLE
// =======
//
// A few pages below, you can find an example program that uses NanoJPEG to
// convert JPEG files into PGM or PPM. To compile it, use something like
// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c
// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :)
///////////////////////////////////////////////////////////////////////////////
// HEADER SECTION //
// copy and pase this into nanojpeg.h if you want //
///////////////////////////////////////////////////////////////////////////////
#ifndef _NANOJPEG_H
#define _NANOJPEG_H
// nj_result_t: Result codes for njDecode().
typedef enum _nj_result {
NJ_OK = 0, // no error, decoding successful
NJ_NO_JPEG, // not a JPEG file
NJ_UNSUPPORTED, // unsupported format
NJ_OUT_OF_MEM, // out of memory
NJ_INTERNAL_ERR, // internal error
NJ_SYNTAX_ERROR, // syntax error
__NJ_FINISHED, // used internally, will never be reported
} nj_result_t;
// njInit: Initialize NanoJPEG.
// For safety reasons, this should be called at least one time before using
// using any of the other NanoJPEG functions.
void njInit(void);
// njDecode: Decode a JPEG image.
// Decodes a memory dump of a JPEG file into internal buffers.
// Parameters:
// jpeg = The pointer to the memory dump.
// size = The size of the JPEG file.
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
nj_result_t njDecode(const void* jpeg, const int size);
// njGetWidth: Return the width (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetWidth() is undefined.
int njGetWidth(void);
// njGetHeight: Return the height (in pixels) of the most recently decoded
// image. If njDecode() failed, the result of njGetHeight() is undefined.
int njGetHeight(void);
// njIsColor: Return 1 if the most recently decoded image is a color image
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
// of njGetWidth() is undefined.
int njIsColor(void);
// njGetImage: Returns the decoded image data.
// Returns a pointer to the most recently image. The memory layout it byte-
// oriented, top-down, without any padding between lines. Pixels of color
// images will be stored as three consecutive bytes for the red, green and
// blue channels. This data format is thus compatible with the PGM or PPM
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
// If njDecode() failed, the result of njGetImage() is undefined.
unsigned char* njGetImage(void);
// njGetImageSize: Returns the size (in bytes) of the image data returned
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
// undefined.
int njGetImageSize(void);
// njDone: Uninitialize NanoJPEG.
// Resets NanoJPEG's internal state and frees all memory that has been
// allocated at run-time by NanoJPEG. It is still possible to decode another
// image after a njDone() call.
void njDone(void);
#endif//_NANOJPEG_H
///////////////////////////////////////////////////////////////////////////////
// CONFIGURATION SECTION //
// adjust the default settings for the NJ_ defines here //
///////////////////////////////////////////////////////////////////////////////
#ifndef NJ_USE_LIBC
#define NJ_USE_LIBC 1
#endif
#ifndef NJ_USE_WIN32
#ifdef _MSC_VER
#define NJ_USE_WIN32 (!NJ_USE_LIBC)
#else
#define NJ_USE_WIN32 0
#endif
#endif
#ifndef NJ_CHROMA_FILTER
#define NJ_CHROMA_FILTER 1
#endif
///////////////////////////////////////////////////////////////////////////////
// EXAMPLE PROGRAM //
// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) //
///////////////////////////////////////////////////////////////////////////////
#ifdef _NJ_EXAMPLE_PROGRAM
#include <stdio.h>
#include "libc/stdlib.h"
#include "libc/string.h"
int main(int argc, char* argv[]) {
int size;
char *buf;
FILE *f;
if (argc < 2) {
printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]);
return 2;
}
f = fopen(argv[1], "rb");
if (!f) {
printf("Error opening the input file.\n");
return 1;
}
fseek(f, 0, SEEK_END);
size = (int) ftell(f); // 字节
buf = malloc(size);
fseek(f, 0, SEEK_SET);
size = (int) fread(buf, 1, size, f); // 读取整个文件内容到buf
fclose(f);
njInit(); // 初始化nj_context_t
if (njDecode(buf, size)) {
printf("Error decoding the input file.\n");
return 1;
}
f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb");
if (!f) {
printf("Error opening the output file.\n");
return 1;
}
fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight());
fwrite(njGetImage(), 1, njGetImageSize(), f);
fclose(f);
njDone();
return 0;
}
#endif
// 解释什么是stride http://msdn.microsoft.com/en-us/library/windows/desktop/aa473780(v=vs.85).aspx
///////////////////////////////////////////////////////////////////////////////
// IMPLEMENTATION SECTION //
// you may stop reading here //
///////////////////////////////////////////////////////////////////////////////
#ifndef _NJ_INCLUDE_HEADER_ONLY
#include <stddef.h> // For NULL in freestanding mode
#ifdef _MSC_VER
#define NJ_INLINE static __inline
#define NJ_FORCE_INLINE static __forceinline
#else
#define NJ_INLINE static inline
#define NJ_FORCE_INLINE static inline
#endif
#if NJ_USE_LIBC
#include "libc/stdlib.h"
#define njAllocMem malloc
#define njFreeMem free
#define njFillMem memset
#define njCopyMem memcpy
#elif NJ_USE_WIN32
#include <windows.h>
#define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size)))
#define njFreeMem(block) ((void) LocalFree((HLOCAL) block))
NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm {
mov edi, block
mov al, value
mov ecx, count
rep stosb
} }
NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm {
mov edi, dest
mov esi, src
mov ecx, count
rep movsb
} }
#else
extern void* njAllocMem(int size);
extern void njFreeMem(void* block);
extern void njFillMem(void* block, unsigned char byte, int size);
extern void njCopyMem(void* dest, const void* src, int size);
#endif
typedef struct _nj_code {
unsigned char bits, code;
} nj_vlc_code_t;
typedef struct _nj_cmp {
int cid;
int ssx, ssy; // 水平/垂直因子
int width, height;
int stride;
int qtsel; // Quantization Table量化表
int actabsel, dctabsel; // AC/DC Huffman Table
int dcpred; // DC prediction
unsigned char *pixels;
} nj_component_t; // 颜色分量
typedef struct _nj_ctx {
nj_result_t error;
const unsigned char *pos; // 待解码数据指针(按字节来)
int size; // 整个数据的长度
int length; // 某一个marker内容的长度
int width, height; // 图片宽和高度
int mbwidth, mbheight; // MCU水平/垂直个数
int mbsizex, mbsizey; // MCU宽/高
int ncomp; // 颜色分量数
nj_component_t comp[3]; // YCbCr
int qtused, qtavail; // 这两个目前看不出来很大用处
unsigned char qtab[4][64]; // 但是目前似乎只有2个
nj_vlc_code_t vlctab[4][65536]; // 构造所有16位数的Huffman基数
// 目前基本上是4个(直/交/0/1)
int buf, bufbits; // 这是用来做什么的 buf是存放内容的 bufbits是计数器存放了多少个bits
int block[64];
int rstinterval;
unsigned char *rgb; // 解析出来的RGB所要占用的内存 // 每1个点包含3个字节按找RGB的顺序
} nj_context_t;
static nj_context_t nj;
static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18,
11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35,
42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };
/*
0 1 2 3 4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
*/
NJ_FORCE_INLINE unsigned char njClip(const int x) { // 限定范围是0 ~ 255之间
return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x);
}
#define W1 2841
#define W2 2676
#define W3 2408
#define W5 1609
#define W6 1108
#define W7 565
NJ_INLINE void njRowIDCT(int* blk) { // 按行来操作的 0 ~ 7 // 8 ~ 15
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
if (!((x1 = blk[4] << 11)
| (x2 = blk[6])
| (x3 = blk[2])
| (x4 = blk[1])
| (x5 = blk[7])
| (x6 = blk[5])
| (x7 = blk[3])))
{
blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
return;
}
x0 = (blk[0] << 11) + 128;
x8 = W7 * (x4 + x5);
x4 = x8 + (W1 - W7) * x4;
x5 = x8 - (W1 + W7) * x5;
x8 = W3 * (x6 + x7);
x6 = x8 - (W3 - W5) * x6;
x7 = x8 - (W3 + W5) * x7;
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2);
x2 = x1 - (W2 + W6) * x2;
x3 = x1 + (W2 - W6) * x3;
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = (181 * (x4 + x5) + 128) >> 8;
x4 = (181 * (x4 - x5) + 128) >> 8;
blk[0] = (x7 + x1) >> 8;
blk[1] = (x3 + x2) >> 8;
blk[2] = (x0 + x4) >> 8;
blk[3] = (x8 + x6) >> 8;
blk[4] = (x8 - x6) >> 8;
blk[5] = (x0 - x4) >> 8;
blk[6] = (x3 - x2) >> 8;
blk[7] = (x7 - x1) >> 8;
}
NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) {
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
if (!((x1 = blk[8*4] << 8)
| (x2 = blk[8*6])
| (x3 = blk[8*2])
| (x4 = blk[8*1])
| (x5 = blk[8*7])
| (x6 = blk[8*5])
| (x7 = blk[8*3])))
{
x1 = njClip(((blk[0] + 32) >> 6) + 128);
for (x0 = 8; x0; --x0) {
*out = (unsigned char) x1;
out += stride;
}
return;
}
x0 = (blk[0] << 8) + 8192;
x8 = W7 * (x4 + x5) + 4;
x4 = (x8 + (W1 - W7) * x4) >> 3;
x5 = (x8 - (W1 + W7) * x5) >> 3;
x8 = W3 * (x6 + x7) + 4;
x6 = (x8 - (W3 - W5) * x6) >> 3;
x7 = (x8 - (W3 + W5) * x7) >> 3;
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2) + 4;
x2 = (x1 - (W2 + W6) * x2) >> 3;
x3 = (x1 + (W2 - W6) * x3) >> 3;
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = (181 * (x4 + x5) + 128) >> 8; // YCb和Cr的值都范围都是-128 ~ 127并且在FDCT的时候有先减去128所以现在要IDCT之后再加上128
x4 = (181 * (x4 - x5) + 128) >> 8;
*out = njClip(((x7 + x1) >> 14) + 128); out += stride;
*out = njClip(((x3 + x2) >> 14) + 128); out += stride;
*out = njClip(((x0 + x4) >> 14) + 128); out += stride;
*out = njClip(((x8 + x6) >> 14) + 128); out += stride;
*out = njClip(((x8 - x6) >> 14) + 128); out += stride;
*out = njClip(((x0 - x4) >> 14) + 128); out += stride;
*out = njClip(((x3 - x2) >> 14) + 128); out += stride;
*out = njClip(((x7 - x1) >> 14) + 128);
}
#define njThrow(e) do { nj.error = e; return; } while (0)
#define njCheckError() do { if (nj.error) return; } while (0)
static int njShowBits(int bits) { // 能放得下大于32位的值么
unsigned char newbyte;
if (!bits) return 0;
while (nj.bufbits < bits) { // 也就是说要buf的位数小于已经buf的位数的时候就直接读出来
if (nj.size <= 0) {
nj.buf = (nj.buf << 8) | 0xFF;
nj.bufbits += 8;
continue;
}
newbyte = *nj.pos++; // 数据指针是按字节
nj.size--;
nj.bufbits += 8;
nj.buf = (nj.buf << 8) | newbyte; // 高位最终会被覆盖掉比如我要buf一个64位的值怎么办
if (newbyte == 0xFF) {
if (nj.size) {
unsigned char marker = *nj.pos++;
nj.size--;
switch (marker) {
case 0x00:
case 0xFF:
break;
case 0xD9: nj.size = 0; break;
default:
if ((marker & 0xF8) != 0xD0)
nj.error = NJ_SYNTAX_ERROR;
else {
nj.buf = (nj.buf << 8) | marker;
nj.bufbits += 8;
}
}
} else
nj.error = NJ_SYNTAX_ERROR;
}
}
return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1);
}
NJ_INLINE void njSkipBits(int bits) {
if (nj.bufbits < bits)
(void) njShowBits(bits);
nj.bufbits -= bits;
}
NJ_INLINE int njGetBits(int bits) {
int res = njShowBits(bits);
njSkipBits(bits);
return res;
}
NJ_INLINE void njByteAlign(void) {
nj.bufbits &= 0xF8; // (1111 1000)8的倍数不满8的部分丢弃
}
static void njSkip(int count) {
nj.pos += count; // 数据指针增加
nj.size -= count; // 总体数据大小减去count
nj.length -= count; // 当前marker长度减去count
if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR;
}
NJ_INLINE unsigned short njDecode16(const unsigned char *pos) {
return (pos[0] << 8) | pos[1]; // 00000000 00001101
}
static void njDecodeLength(void) { // decode长度字段这个方法调用一般都是已经进入到特定的marker之后
if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR);
nj.length = njDecode16(nj.pos); // 该marker的长度(除去marker名字所占用的2个字节)
if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR);
njSkip(2);
}
NJ_INLINE void njSkipMarker(void) {
njDecodeLength();
njSkip(nj.length);
}
NJ_INLINE void njDecodeSOF(void) { // 解析Start of Frame的时候就会把所需要的内存都分配好
int i, ssxmax = 0, ssymax = 0;
nj_component_t* c;
njDecodeLength(); // 解析长度并移动数据指针
if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR);
if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); // 样本精度一般都是8
nj.height = njDecode16(nj.pos + 1); // 图片高度/宽度
nj.width = njDecode16(nj.pos + 3);
nj.ncomp = nj.pos[5]; // 颜色分量数据一般都是3
njSkip(6); // 之前共6个字节数据所以移动数据指针6个字节
switch (nj.ncomp) { // 目前只支持1和3这两种
case 1:
case 3:
break;
default:
njThrow(NJ_UNSUPPORTED);
}
if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); // 数据量肯定是要大于颜色分量数 multiply 3因为接着存颜色分量信息的每个结构占3个字节
// 颜色分量ID占用1个字节水平/垂直因子占用1个字节(高4位水平低4位垂直)量化表占用1个字节
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
c->cid = nj.pos[0]; // 颜色分量ID
if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); // 高4位(水平因子)
if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); // (00001111)低4位(垂直因子)
if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (11111101) 这里0xFC是用在这里干什么的
njSkip(3); // 移动数据指针到下一个颜色分量
nj.qtused |= 1 << c->qtsel; // 这里是做什么用的?看不出来
if (c->ssx > ssxmax) ssxmax = c->ssx; // 记录最大水平因子
if (c->ssy > ssymax) ssymax = c->ssy; // 记录最大垂直因子
}
if (nj.ncomp == 1) { // 只有一种颜色分量的时候就简单啦
c = nj.comp;
c->ssx = c->ssy = ssxmax = ssymax = 1;
}
nj.mbsizex = ssxmax << 3; // MCU宽 是 水平采样因子最大值 multiply 8
nj.mbsizey = ssymax << 3; // MCU高 是 垂直采样因子最大值 multiply 8
nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; // 分子采用+ nj.mbsizex - 1就取到大于但是最接近(等于)宽度的值,
// 并且这个值是MCU宽度整数倍 // 这里是水平方向MCU的个数
nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; // 这里是垂直方向MCU的个数
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; // 采样宽度? 最大水平/垂直因子的值就是图片原来的值,否则就会根据因子做相应的减少
c->stride = (c->width + 7) & 0x7FFFFFF8; // (0111 1111 1111 1111 1111 1111 1111 1000) 做什么以1234567结尾的都省略掉
// 变成8的整数
// 补齐8位注意前面有加7所以总是不会比原来的少比如原来是227那么这里就会变成232
// 这是按照数据单元计算的,所以不对
c->height = (nj.height * c->ssy + ssymax - 1) / ssymax;
c->stride = nj.mbwidth * nj.mbsizex * c->ssx / ssxmax; // 再计算一遍stride有什么用前面计算的是错误的没有考虑MCU宽度
// 这里都已经是round过的了所以直接计算
if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED);
if (!(c->pixels = njAllocMem(c->stride * (nj.mbheight * nj.mbsizey * c->ssy / ssymax)))) njThrow(NJ_OUT_OF_MEM); // 为分量分配内存
// 大小是所有MCU的
// 可能比图片实际
// 尺寸大
}
if (nj.ncomp == 3) { // 只有有3个颜色分量的时候才需要
nj.rgb = njAllocMem(nj.width * nj.height * nj.ncomp);
if (!nj.rgb) njThrow(NJ_OUT_OF_MEM);
}
njSkip(nj.length);
}
NJ_INLINE void njDecodeDHT(void) {
int codelen, currcnt, remain, spread, i, j;
nj_vlc_code_t *vlc;
static unsigned char counts[16]; // 码字
njDecodeLength();
while (nj.length >= 17) { // 码字的数量(16) + 类型和ID(1)
i = nj.pos[0]; // 类型和ID
if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); // (11101100)
if (i & 0x02) njThrow(NJ_UNSUPPORTED); // (00000010)
i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value
// 直流0直流1交流0交流1
for (codelen = 1; codelen <= 16; ++codelen) // 码字长度
counts[codelen - 1] = nj.pos[codelen]; // 读取码字 DHT 当中的16个字节 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00
njSkip(17);
vlc = &nj.vlctab[i][0];
remain = spread = 65536;
for (codelen = 1; codelen <= 16; ++codelen) {
spread >>= 1; // 干什么? // 65536 >> 16 = 1 每个category所包含的编码个数
currcnt = counts[codelen - 1];
if (!currcnt) continue; // 如果该位数没有码字
if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR);
remain -= currcnt << (16 - codelen); // 干什么? 计算当前size的码字占用多少VLC表的空间得到剩下的空间
if (remain < 0) njThrow(NJ_SYNTAX_ERROR);
for (i = 0; i < currcnt; ++i) { // 码字个数,同样位数的码字可以有多个
register unsigned char code = nj.pos[i]; // 有多少个就,读多少个字节
for (j = spread; j; --j) { // 保存这么多个有什么作用?
vlc->bits = (unsigned char) codelen; // 码字位数
vlc->code = code; // 码字值(这个读取出来的到底是什么00 01 02 03 04 05 06 07 08 09 0A 0B是值还是权重)
++vlc;
}
}
njSkip(currcnt);
}
while (remain--) { // 16位都填充完成剩下的就用0填(1位码字XX个2位码字XX个...)
// printf("i'm nothing vlc id %d\n", tblid);
vlc->bits = 0;
++vlc;
}
// for debug
// printf("Huffman vlc id %d\n", tblid);
// njPrintHT(tblid);
}
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}
NJ_INLINE void njDecodeDQT(void) {
int i;
unsigned char *t;
njDecodeLength();
while (nj.length >= 65) {
i = nj.pos[0]; // QT信息高4位为QT精度低4位为QT号
if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (1111 1110)这个用来检测QT号码是否正确的吗目前精度好像都为0所以这么写
nj.qtavail |= 1 << i; // XXX 直接通过这里转换为数量?
t = &nj.qtab[i][0];
for (i = 0; i < 64; ++i)
t[i] = nj.pos[i + 1]; // 读取到QT数组当中但应该还是按照文件流当中的排列
njSkip(65);
}
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
}
NJ_INLINE void njDecodeDRI(void) {
njDecodeLength();
if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR);
nj.rstinterval = njDecode16(nj.pos);
njSkip(nj.length);
}
static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { // Variable Length Coding
int value = njShowBits(16); // 为什么是2个字节 这又是什么? 或许是这里的Huffman编码的码字永远是少于16位的
int bits = vlc[value].bits;
if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; }
njSkipBits(bits);
value = vlc[value].code;
if (code) *code = (unsigned char) value;
bits = value & 15; // 这个value必须是0~15之间
if (!bits) {
return 0;
}
value = njGetBits(bits); // 如果这里需要读取的值的位数超过之前njShowBits剩余的值这里会重新读取
if (value < (1 << (bits - 1)))
value += ((-1) << bits) + 1;
return value;
}
NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { // 8 x 8
unsigned char code = 0;
int value, coef = 0;
njFillMem(nj.block, 0, sizeof(nj.block));
int dcvlcval = njGetVLC(&nj.vlctab[c->dctabsel][0], NULL);
c->dcpred += dcvlcval;
nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; // DC // 这里是反量化?
do {
value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); // DC 2/3
if (!code) break; // EOB
if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); // 这是什么字段?(难道是为了兼容这个过程中可以遇到0xF0这样的数据)
coef += (code >> 4) + 1; // coefficient 系数
if (coef > 63) njThrow(NJ_SYNTAX_ERROR);
nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; // AC 这里是反量化?
} while (coef < 63);
for (coef = 0; coef < 64; coef += 8)
njRowIDCT(&nj.block[coef]); // 上面先Huffman解码/反量化,这里行(反DCT)
for (coef = 0; coef < 8; ++coef)
njColIDCT(&nj.block[coef], &out[coef], c->stride);
}
NJ_INLINE void njDecodeScan(void) {
// njPrintHT(0);
// njPrintHT(2);
// njPrintHT(1);
// njPrintHT(3);
int i, mbx, mby, sbx, sby;
int rstcount = nj.rstinterval, nextrst = 0;
nj_component_t* c;
njDecodeLength();
if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR);
if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED);
njSkip(1); // 颜色分量数量
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); // 颜色分量ID
if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR);
c->dctabsel = nj.pos[1] >> 4; // 高4位为直流表DC Table
c->actabsel = (nj.pos[1] & 1) | 2; // 低4位为交流表AC Table(这里有做特殊处理所以AC的表名不会和DC相同)
njSkip(2);
}
if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED);
njSkip(nj.length); // 忽略3个字节 通常为 00 3F 00
// 2 + 1 + 6 + 3为12字节这个marker的长度刚好为12字节
// 接下来都是编码过的图像数据
for (mbx = mby = 0;;) {
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) // 每个分量都要decode
for (sby = 0; sby < c->ssy; ++sby) // 水平/垂直因子
for (sbx = 0; sbx < c->ssx; ++sbx) {
njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]); // 读取原始编码过
// 的图片数据到block中
// 并反量化,反离散余弦变换
njCheckError();
}
if (++mbx >= nj.mbwidth) { // 读完所有的MCU到达最右就返回从下一行开始
mbx = 0;
if (++mby >= nj.mbheight) break; // 到达最底行的时候推出decode结束
}
if (nj.rstinterval && !(--rstcount)) { // restart marker
njByteAlign();
i = njGetBits(16);
if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR);
nextrst = (nextrst + 1) & 7;
rstcount = nj.rstinterval;
for (i = 0; i < 3; ++i)
nj.comp[i].dcpred = 0;
}
}
nj.error = __NJ_FINISHED;
}
#if NJ_CHROMA_FILTER
#define CF4A (-9)
#define CF4B (111)
#define CF4C (29)
#define CF4D (-3)
#define CF3A (28)
#define CF3B (109)
#define CF3C (-9)
#define CF3X (104)
#define CF3Y (27)
#define CF3Z (-3)
#define CF2A (139)
#define CF2B (-11)
#define CF(x) njClip(((x) + 64) >> 7)
// 通常我们放大图片的时候就需要upsampling缩小的时候就downsampling通称为resampling
// 这里Cb/Cr分量的会少些所以需要upsampling
NJ_INLINE void njUpsampleH(nj_component_t* c) {
const int xmax = c->width - 3;
unsigned char *out, *lin, *lout;
int x, y;
out = njAllocMem((c->width * c->height) << 1);
if (!out) njThrow(NJ_OUT_OF_MEM);
lin = c->pixels;
lout = out;
for (y = c->height; y; --y) {
lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
for (x = 0; x < xmax; ++x) {
lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]);
lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]);
}
lin += c->stride;
lout += c->width << 1;
lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
}
c->width <<= 1;
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
NJ_INLINE void njUpsampleV(nj_component_t* c) {
const int w = c->width, s1 = c->stride, s2 = s1 + s1;
unsigned char *out, *cin, *cout;
int x, y;
out = njAllocMem((c->width * c->height) << 1);
if (!out) njThrow(NJ_OUT_OF_MEM);
for (x = 0; x < w; ++x) {
cin = &c->pixels[x];
cout = &out[x];
*cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w;
*cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w;
*cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w;
cin += s1;
for (y = c->height - 3; y; --y) {
*cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w;
*cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w;
cin += s1;
}
cin += s1;
*cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w;
*cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w;
*cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
}
c->height <<= 1;
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
#else
NJ_INLINE void njUpsample(nj_component_t* c) {
int x, y, xshift = 0, yshift = 0;
unsigned char *out, *lin, *lout;
while (c->width < nj.width) { c->width <<= 1; ++xshift; }
while (c->height < nj.height) { c->height <<= 1; ++yshift; }
out = njAllocMem(c->width * c->height); // 放大后的尺寸
if (!out) njThrow(NJ_OUT_OF_MEM);
lin = c->pixels;
lout = out;
for (y = 0; y < c->height; ++y) {
lin = &c->pixels[(y >> yshift) * c->stride];
for (x = 0; x < c->width; ++x)
lout[x] = lin[x >> xshift];
lout += c->width;
}
c->stride = c->width;
njFreeMem(c->pixels);
c->pixels = out;
}
#endif
NJ_INLINE void njConvert() {
int i;
nj_component_t* c;
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { // 如果需要的话就upsampling
#if NJ_CHROMA_FILTER
while ((c->width < nj.width) || (c->height < nj.height)) {
if (c->width < nj.width) njUpsampleH(c);
njCheckError();
if (c->height < nj.height) njUpsampleV(c);
njCheckError();
}
#else
if ((c->width < nj.width) || (c->height < nj.height))
njUpsample(c);
#endif
if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR);
}
if (nj.ncomp == 3) { // SEE njGetImage()
// convert to RGB
int x, yy;
unsigned char *prgb = nj.rgb;
const unsigned char *py = nj.comp[0].pixels;
const unsigned char *pcb = nj.comp[1].pixels;
const unsigned char *pcr = nj.comp[2].pixels;
// 多余的数据(编/解码是对齐用的)会被丢弃吗?
for (yy = nj.height; yy; --yy) { // 列
for (x = 0; x < nj.width; ++x) { // 行
register int y = py[x] << 8; // 这是为什么? 色彩空间转换公式计算需要
register int cb = pcb[x] - 128; // YCbCr的Cb和Cr一般都是有符号数但是在JPEG当中都是无符号数
register int cr = pcr[x] - 128;
*prgb++ = njClip((y + 359 * cr + 128) >> 8); // 色彩空间转换YCbCr到RGB
*prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8);
*prgb++ = njClip((y + 454 * cb + 128) >> 8);
}
py += nj.comp[0].stride; // 移动YCbCr数据指针每一行都是有stride的所以当需要的数据都得到时后面的就不管直接丢弃移动到下一行
pcb += nj.comp[1].stride;
pcr += nj.comp[2].stride;
}
} else if (nj.comp[0].width != nj.comp[0].stride) { // 如果宽度和stride都一样什么都不用做
// grayscale -> only remove stride
unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride];
unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width];
int y;
for (y = nj.comp[0].height - 1; y; --y) {
njCopyMem(pout, pin, nj.comp[0].width);
pin += nj.comp[0].stride;
pout += nj.comp[0].width;
}
nj.comp[0].stride = nj.comp[0].width;
}
}
void njInit(void) {
njFillMem(&nj, 0, sizeof(nj_context_t)); // 初始化nj_context_t
}
void njDone(void) {
int i;
for (i = 0; i < 3; ++i)
if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels);
if (nj.rgb) njFreeMem((void*) nj.rgb);
njInit();
}
nj_result_t njDecode(const void* jpeg, const int size) {
njDone();
nj.pos = (const unsigned char*) jpeg;
nj.size = size & 0x7FFFFFFF; //
if (nj.size < 2) return NJ_NO_JPEG;
if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; // 不以0xFFD8打头(为什么要用异或来判断?)
njSkip(2);
while (!nj.error) { // 有“错误”的时候离开
if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; // 太小或者不以0xFF打头
njSkip(2); // 移动到标签的后面(长度字段的前面)
switch (nj.pos[-1]) {
case 0xC0: njDecodeSOF(); break;
case 0xC4: njDecodeDHT(); break;
case 0xDB: njDecodeDQT(); break;
case 0xDD: njDecodeDRI(); break;
case 0xDA: njDecodeScan(); break;
case 0xFE: njSkipMarker(); break;
default:
if ((nj.pos[-1] & 0xF0) == 0xE0) // JPG0和APP0字段目前都忽略
njSkipMarker();
else
return NJ_UNSUPPORTED;
}
}
if (nj.error != __NJ_FINISHED) return nj.error;
nj.error = NJ_OK;
njConvert();
return nj.error;
}
int njGetWidth(void) { return nj.width; }
int njGetHeight(void) { return nj.height; }
int njIsColor(void) { return (nj.ncomp != 1); }
unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } // 一/三个分量
int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; }
#endif // _NJ_INCLUDE_HEADER_ONLY

View file

@ -1,10 +0,0 @@
// nanojpeg.h - Header for NanoJPEG decoder (freestanding kernel use)
#ifndef NANOJPEG_H
#define NANOJPEG_H
// Include naojpeg.c in header-only mode to get the type/function declarations
#define _NJ_INCLUDE_HEADER_ONLY
#include "nanojpeg.c"
#undef _NJ_INCLUDE_HEADER_ONLY
#endif // NANOJPEG_H

View file

@ -4,7 +4,7 @@
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"
#include "nanojpeg.h"
#include "stb_image.h"
#include <stddef.h>
#include <stdint.h>
@ -113,13 +113,17 @@ static void generate_lumberjack_pattern(void) {
}
}
static void scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h, uint32_t *dst, int dst_w, int dst_h) {
static void scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h, uint32_t *dst, int dst_w, int dst_h) {
for (int y = 0; y < dst_h; y++) {
int src_y = y * src_h / dst_h;
for (int x = 0; x < dst_w; x++) {
int src_x = x * src_w / dst_w;
int idx = (src_y * src_w + src_x) * 3;
dst[y * dst_w + x] = 0xFF000000 | (rgb[idx] << 16) | (rgb[idx + 1] << 8) | rgb[idx + 2];
int idx = (src_y * src_w + src_x) * 4;
uint8_t r = rgba[idx];
uint8_t g = rgba[idx + 1];
uint8_t b = rgba[idx + 2];
uint8_t a = rgba[idx + 3];
dst[y * dst_w + x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
}
}
@ -161,12 +165,13 @@ static void load_wallpapers(void) {
unsigned char *buf = (unsigned char *)malloc(size);
if (buf) {
sys_read(fd, buf, size);
njInit();
if (njDecode(buf, size) == NJ_OK) {
scale_rgb_to_argb(njGetImage(), njGetWidth(), njGetHeight(), wp->thumb, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H);
int img_w, img_h, channels;
unsigned char *img = stbi_load_from_memory(buf, size, &img_w, &img_h, &channels, 4);
if (img && img_w > 0 && img_h > 0) {
scale_rgba_to_argb(img, img_w, img_h, wp->thumb, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H);
wp->valid = 1;
stbi_image_free(img);
}
njDone();
free(buf); // Release memory
}
}

View file

@ -0,0 +1,5 @@
// stb_image.c wrapper for Userland Mode
#include <stdlib.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

View file

@ -1,3 +1,17 @@
#ifndef BOREDOS_STBI_CONFIG
#define BOREDOS_STBI_CONFIG
#define STBI_NO_STDIO
#define STBI_NO_LINEAR
#define STBI_NO_HDR
#define STBI_NO_SIMD
#define STBI_NO_PSD
#define STBI_NO_PIC
#define STBI_NO_PNM
#define STBI_NO_THREAD_LOCALS
#define STBI_ASSERT(x) ((void)(x))
static inline int abs_stbi(int x) { return x < 0 ? -x : x; }
#define abs abs_stbi
#endif
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk

View file

@ -1,7 +1,7 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "nanojpeg.h"
#include "stb_image.h"
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"
@ -13,6 +13,12 @@
#define VIEWER_MAX_H 600
static uint32_t *viewer_pixels = NULL;
static uint32_t **viewer_frames = NULL;
static int *viewer_delays = NULL;
static int viewer_frame_count = 0;
static int viewer_current_frame = 0;
static uint64_t viewer_next_frame_tick = 0;
static int viewer_img_w = 0;
static int viewer_img_h = 0;
static char viewer_title[64] = "Viewer";
@ -33,20 +39,43 @@ static int viewer_strlen(const char *s) {
return len;
}
static void viewer_scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h,
uint32_t *dst, int dst_w, int dst_h) {
for (int y = 0; y < dst_h; y++) {
int src_y = y * src_h / dst_h;
if (src_y >= src_h) src_y = src_h - 1;
for (int x = 0; x < dst_w; x++) {
int src_x = x * src_w / dst_w;
if (src_x >= src_w) src_x = src_w - 1;
int idx = (src_y * src_w + src_x) * 3;
unsigned char r = rgb[idx];
unsigned char g = rgb[idx + 1];
unsigned char b = rgb[idx + 2];
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
if (src_w == dst_w && src_h == dst_h) {
// Fast path: 1:1 copy
for (int i = 0; i < dst_w * dst_h; i++) {
int idx = i * 4;
dst[i] = ((uint32_t)rgba[idx + 3] << 24) | ((uint32_t)rgba[idx] << 16) |
((uint32_t)rgba[idx + 1] << 8) | rgba[idx + 2];
}
return;
}
// Fixed-point 16.16
uint32_t step_x = (src_w << 16) / dst_w;
uint32_t step_y = (src_h << 16) / dst_h;
uint32_t curr_y = 0;
for (int y = 0; y < dst_h; y++) {
uint32_t src_y = curr_y >> 16;
if (src_y >= (uint32_t)src_h) src_y = src_h - 1;
uint32_t curr_x = 0;
uint32_t src_row_offset = src_y * src_w;
uint32_t dst_row_offset = y * dst_w;
for (int x = 0; x < dst_w; x++) {
uint32_t src_x = curr_x >> 16;
if (src_x >= (uint32_t)src_w) src_x = src_w - 1;
int idx = (src_row_offset + src_x) * 4;
dst[dst_row_offset + x] = ((uint32_t)rgba[idx + 3] << 24) |
((uint32_t)rgba[idx] << 16) |
((uint32_t)rgba[idx + 1] << 8) |
rgba[idx + 2];
curr_x += step_x;
}
curr_y += step_y;
}
}
@ -56,13 +85,15 @@ static void viewer_paint(ui_window_t win) {
int cw = win_w - 8;
int ch = win_h - 28;
ui_draw_rect(win, cx, cy, cw, ch, 0xFF1A1A1A);
if (!viewer_has_image) {
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
return;
}
uint32_t *pixels = viewer_pixels;
if (viewer_frames) pixels = viewer_frames[viewer_current_frame];
int disp_w = viewer_img_w;
int disp_h = viewer_img_h;
@ -76,62 +107,58 @@ static void viewer_paint(ui_window_t win) {
}
int ox = cx + (cw - disp_w) / 2;
int oy = cy + (ch - disp_h - 30) / 2;
int oy = cy + (ch - disp_h) / 2;
uint32_t *temp_buf = malloc(disp_w * disp_h * sizeof(uint32_t));
if (temp_buf) {
if (disp_w == viewer_img_w && disp_h == viewer_img_h) {
// Fast path: 1:1
for (int i = 0; i < disp_w * disp_h; i++) temp_buf[i] = pixels[i];
} else {
// Fixed-point 16.16
uint32_t step_x = (viewer_img_w << 16) / disp_w;
uint32_t step_y = (viewer_img_h << 16) / disp_h;
uint32_t curr_y = 0;
for (int y = 0; y < disp_h; y++) {
int src_y = y * viewer_img_h / disp_h;
if (src_y >= viewer_img_h) src_y = viewer_img_h - 1;
uint32_t src_y = curr_y >> 16;
if (src_y >= (uint32_t)viewer_img_h) src_y = viewer_img_h - 1;
uint32_t curr_x = 0;
uint32_t src_row_off = src_y * viewer_img_w;
uint32_t dst_row_off = y * disp_w;
for (int x = 0; x < disp_w; x++) {
int src_x = x * viewer_img_w / disp_w;
if (src_x >= viewer_img_w) src_x = viewer_img_w - 1;
temp_buf[y * disp_w + x] = viewer_pixels[src_y * viewer_img_w + src_x];
uint32_t src_x = curr_x >> 16;
if (src_x >= (uint32_t)viewer_img_w) src_x = viewer_img_w - 1;
temp_buf[dst_row_off + x] = pixels[src_row_off + src_x];
curr_x += step_x;
}
curr_y += step_y;
}
}
ui_draw_image(win, ox, oy, disp_w, disp_h, temp_buf);
free(temp_buf);
}
int btn_w = 160;
int btn_h = 22;
int btn_x = cx + (cw - btn_w) / 2;
int btn_y = (win_h - 20) - 30;
ui_draw_rounded_rect_filled(win, btn_x, btn_y, btn_w, btn_h, 6, 0xFF2D2D2D);
ui_draw_string(win, btn_x + 10, btn_y + 6, "Set as Wallpaper", 0xFFF0F0F0);
}
static void viewer_handle_click(ui_window_t win, int x, int y) {
if (!viewer_has_image) return;
int cx = 4;
int cw = win_w - 8;
int btn_w = 160;
int btn_x = cx + (cw - btn_w) / 2;
int btn_y = (win_h - 25) - 30; // Matches the hitboxes
if (x >= btn_x && x < btn_x + btn_w && y >= btn_y && y < btn_y + 22) {
// SYSTEM_CMD_SET_WALLPAPER_PATH is 31
sys_system(31, (uint64_t)viewer_file_path, 0, 0, 0);
}
}
void viewer_open_file(const char *path) {
int fd = sys_open(path, "r");
if (fd < 0) return;
// We can't use stat yet, so read chunks until EOF
// Alternatively, use a large buffer if sys_read handles large chunks.
int alloc_size = 2 * 1024 * 1024;
unsigned char *buf = malloc(alloc_size);
uint32_t file_size = sys_size(fd);
if (file_size == 0 || file_size > 32 * 1024 * 1024) { // 32MB limit
sys_close(fd);
return;
}
unsigned char *buf = malloc(file_size);
if (!buf) {
sys_close(fd);
return;
}
int total_read = 0;
while (total_read < alloc_size) {
int chunk = sys_read(fd, (char*)buf + total_read, alloc_size - total_read);
while (total_read < (int)file_size) {
int chunk = sys_read(fd, (char*)buf + total_read, (int)file_size - total_read);
if (chunk <= 0) break;
total_read += chunk;
}
@ -142,20 +169,35 @@ void viewer_open_file(const char *path) {
return;
}
njInit();
nj_result_t result = njDecode(buf, total_read);
if (result != NJ_OK) {
njDone();
free(buf);
return;
// Free previous image if any
if (viewer_pixels) { free(viewer_pixels); viewer_pixels = NULL; }
if (viewer_frames) {
for (int i = 0; i < viewer_frame_count; i++) {
if (viewer_frames[i]) free(viewer_frames[i]);
}
free(viewer_frames); viewer_frames = NULL;
}
if (viewer_delays) { free(viewer_delays); viewer_delays = NULL; }
viewer_frame_count = 0;
viewer_current_frame = 0;
viewer_has_image = false;
int img_w, img_h, channels;
unsigned char *rgba = NULL;
int *delays = NULL;
int frame_count = 1;
// Check if it's a GIF - more robust check
bool is_gif = (total_read > 4 && buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F');
if (is_gif) {
rgba = stbi_load_gif_from_memory(buf, total_read, &delays, &img_w, &img_h, &frame_count, &channels, 4);
} else {
rgba = stbi_load_from_memory(buf, total_read, &img_w, &img_h, &channels, 4);
}
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *rgb = njGetImage();
if (!rgb || img_w <= 0 || img_h <= 0) {
njDone();
if (!rgba || img_w <= 0 || img_h <= 0) {
if (rgba) stbi_image_free(rgba);
free(buf);
return;
}
@ -171,15 +213,41 @@ void viewer_open_file(const char *path) {
fit_h = VIEWER_MAX_H;
}
if (frame_count > 1 && delays) {
viewer_frames = malloc(frame_count * sizeof(uint32_t *));
viewer_delays = malloc(frame_count * sizeof(int));
if (viewer_frames && viewer_delays) {
viewer_frame_count = frame_count;
for (int i = 0; i < frame_count; i++) {
viewer_frames[i] = malloc(fit_w * fit_h * sizeof(uint32_t));
if (viewer_frames[i]) {
viewer_scale_rgba_to_argb(rgba + (i * img_w * img_h * 4), img_w, img_h, viewer_frames[i], fit_w, fit_h);
viewer_delays[i] = delays[i];
} else {
// Memory exhausted, stop here
viewer_frame_count = i;
break;
}
}
viewer_img_w = fit_w;
viewer_img_h = fit_h;
viewer_has_image = (viewer_frame_count > 0);
if (viewer_has_image) {
viewer_next_frame_tick = sys_system(16, 0, 0, 0, 0) + (viewer_delays[0] * 60 / 1000);
}
}
free(delays);
} else {
viewer_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
if (viewer_pixels) {
viewer_scale_rgb_to_argb(rgb, img_w, img_h, viewer_pixels, fit_w, fit_h);
viewer_scale_rgba_to_argb(rgba, img_w, img_h, viewer_pixels, fit_w, fit_h);
viewer_img_w = fit_w;
viewer_img_h = fit_h;
viewer_has_image = true;
}
}
njDone();
stbi_image_free(rgba);
free(buf);
viewer_strcpy(viewer_file_path, path);
@ -204,7 +272,7 @@ void viewer_open_file(const char *path) {
win_w = fit_w + 16;
if (win_w < 200) win_w = 200;
win_h = fit_h + 64;
win_h = fit_h + 34;
if (win_h < 100) win_h = 100;
}
@ -223,10 +291,23 @@ int main(int argc, char **argv) {
viewer_paint(win);
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
} else if (ev.type == GUI_EVENT_CLICK) {
viewer_handle_click(win, ev.arg1, ev.arg2);
// No actions currently
} else if (ev.type == GUI_EVENT_CLOSE) {
sys_exit(0);
}
} else {
if (viewer_has_image && viewer_frame_count > 1) {
uint64_t now = sys_system(16, 0, 0, 0, 0);
if (now >= viewer_next_frame_tick) {
viewer_current_frame = (viewer_current_frame + 1) % viewer_frame_count;
viewer_next_frame_tick = now + (viewer_delays[viewer_current_frame] * 60 / 1000);
if (viewer_next_frame_tick <= now) viewer_next_frame_tick = now + 1;
viewer_paint(win);
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
}
}
// Small sleep to avoid eating 100% CPU
for (volatile int i = 0; i < 10000; i++);
}
}
return 0;

View file

@ -1,5 +1,6 @@
#include "wallpaper.h"
#include "nanojpeg.h"
#define STBI_NO_STDIO
#include "userland/stb_image.h"
#include "graphics.h"
#include "fat32.h"
#include "memory_manager.h"
@ -10,7 +11,7 @@
// Static buffer for the current wallpaper (max 1920x1080)
#define MAX_WP_WIDTH 1920
#define MAX_WP_HEIGHT 1080
static uint32_t wp_pixels[MAX_WP_WIDTH * MAX_WP_HEIGHT];
static uint32_t* wp_pixels = NULL;
static int wp_width = 0;
static int wp_height = 0;
@ -18,8 +19,8 @@ static int wp_height = 0;
static volatile const char *pending_wallpaper_path = NULL;
static char pending_path_buf[256];
// Simple nearest-neighbor scale from decoded RGB to ARGB pixel buffer
static void scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
// Simple nearest-neighbor scale from decoded RGBA to ARGB pixel buffer
static void scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h,
uint32_t *dst, int dst_w, int dst_h) {
for (int y = 0; y < dst_h; y++) {
int src_y = y * src_h / dst_h;
@ -27,30 +28,24 @@ static void scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
for (int x = 0; x < dst_w; x++) {
int src_x = x * src_w / dst_w;
if (src_x >= src_w) src_x = src_w - 1;
int idx = (src_y * src_w + src_x) * 3;
unsigned char r = rgb[idx];
unsigned char g = rgb[idx + 1];
unsigned char b = rgb[idx + 2];
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
size_t idx = ((size_t)src_y * (size_t)src_w + (size_t)src_x) * 4;
unsigned char r = rgba[idx];
unsigned char g = rgba[idx + 1];
unsigned char b = rgba[idx + 2];
unsigned char a = rgba[idx + 3];
dst[y * dst_w + x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
}
}
// Decode JPEG data from memory and set as wallpaper (MUST be called from non-interrupt context)
static int decode_and_set_wallpaper(const unsigned char *jpg_data, unsigned int jpg_size) {
njInit();
nj_result_t result = njDecode(jpg_data, (int)jpg_size);
if (result != NJ_OK) {
njDone();
return 0;
}
int img_w, img_h, channels;
// We request 4 channels (RGBA) for better alignment and consistency with working userland code
unsigned char *rgba = stbi_load_from_memory(jpg_data, (int)jpg_size, &img_w, &img_h, &channels, 4);
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *rgb = njGetImage();
if (!rgb || img_w <= 0 || img_h <= 0) {
njDone();
if (!rgba || img_w <= 0 || img_h <= 0) {
return 0;
}
@ -60,11 +55,19 @@ static int decode_and_set_wallpaper(const unsigned char *jpg_data, unsigned int
if (screen_w > MAX_WP_WIDTH) screen_w = MAX_WP_WIDTH;
if (screen_h > MAX_WP_HEIGHT) screen_h = MAX_WP_HEIGHT;
scale_rgb_to_argb(rgb, img_w, img_h, wp_pixels, screen_w, screen_h);
if (!wp_pixels) {
wp_pixels = (uint32_t*)kmalloc(MAX_WP_WIDTH * MAX_WP_HEIGHT * sizeof(uint32_t));
if (!wp_pixels) {
stbi_image_free(rgba);
return 0;
}
}
scale_rgba_to_argb(rgba, img_w, img_h, wp_pixels, screen_w, screen_h);
wp_width = screen_w;
wp_height = screen_h;
njDone();
stbi_image_free(rgba);
graphics_set_bg_image(wp_pixels, wp_width, wp_height);
return 1;
@ -113,8 +116,11 @@ void wallpaper_process_pending(void) {
if (fh) {
uint32_t file_size = fh->size;
if (file_size > 0 && file_size <= 4 * 1024 * 1024) {
unsigned char *buf = (unsigned char*)kmalloc(file_size);
// Add padding to avoid stb_image reading past the end and potential corruption
size_t padded_size = file_size + 128;
unsigned char *buf = (unsigned char*)kmalloc(padded_size);
if (buf) {
mem_memset(buf, 0, padded_size);
int total_read = 0;
while (total_read < (int)file_size) {
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);

View file

@ -13,10 +13,21 @@
#include <stddef.h>
#include "wallpaper.h"
#include "fat32.h"
#include "nanojpeg.h"
#define STBI_NO_STDIO
#include "userland/stb_image.h"
#include "memory_manager.h"
#include "disk.h"
// Hello developer,
// i advise you to just not read this code and live on with your life.
// It's not worth it.
// TRUST ME.
// If you do decide to hate yourself for some dumb reason,
// add a few hours to the counter of despair:
// hours wasted: 57
// send help
extern void serial_write(const char *str);
static bool str_eq(const char *s1, const char *s2) {
@ -124,6 +135,15 @@ static bool str_ends_with(const char *str, const char *suffix) {
return true;
}
static bool is_image_file(const char *filename) {
if (!filename) return false;
return str_ends_with(filename, ".jpg") || str_ends_with(filename, ".JPG") ||
str_ends_with(filename, ".png") || str_ends_with(filename, ".PNG") ||
str_ends_with(filename, ".gif") || str_ends_with(filename, ".GIF") ||
str_ends_with(filename, ".bmp") || str_ends_with(filename, ".BMP") ||
str_ends_with(filename, ".tga") || str_ends_with(filename, ".TGA");
}
// Helper to check if string starts with prefix
static bool str_starts_with(const char *str, const char *prefix) {
while(*prefix) {
@ -595,20 +615,17 @@ static uint32_t* thumb_cache_decode(const char *path) {
if (total <= 0) { kfree(buf); return NULL; }
// Decode JPEG
njInit();
if (njDecode(buf, total) != NJ_OK) {
serial_write("[WM] njDecode failed for deferred thumb\n");
njDone();
// Decode image
int img_w, img_h, channels;
unsigned char *img = stbi_load_from_memory(buf, total, &img_w, &img_h, &channels, 4);
if (!img || img_w <= 0 || img_h <= 0) {
serial_write("[WM] stbi_load_from_memory failed for deferred thumb\n");
if (img) stbi_image_free(img);
kfree(buf);
return NULL;
}
serial_write("[WM] njDecode OK for deferred thumb\n");
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *img = njGetImage();
serial_write("[WM] stbi_load_from_memory OK for deferred thumb\n");
// Store in cache — downscale to 48x48
int slot = thumb_cache_next;
@ -626,16 +643,16 @@ static uint32_t* thumb_cache_decode(const char *path) {
int sy = ty * img_h / 48;
if (sx >= img_w) sx = img_w - 1;
if (sy >= img_h) sy = img_h - 1;
int idx = (sy * img_w + sx) * 3;
uint32_t r = img[idx], g = img[idx+1], b = img[idx+2];
thumb_cache[slot].pixels[ty * 48 + tx] = 0xFF000000 | (r << 16) | (g << 8) | b;
int idx = (sy * img_w + sx) * 4;
uint32_t r = img[idx], g = img[idx+1], b = img[idx+2], a = img[idx+3];
thumb_cache[slot].pixels[ty * 48 + tx] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
thumb_cache[slot].valid = true;
thumb_cache[slot].failed = false;
njDone();
stbi_image_free(img);
kfree(buf);
return thumb_cache[slot].pixels;
@ -1226,7 +1243,7 @@ void wm_paint(void) {
} else {
if (str_ends_with(icon->name, ".elf")) draw_elf_icon(icon->x, icon->y, icon->name);
else if (str_ends_with(icon->name, ".pnt")) draw_paint_icon(icon->x, icon->y, icon->name);
else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
else if (is_image_file(icon->name)) {
char full_path[128] = "/Desktop/";
int p=9; int n=0; while(icon->name[n] && p < 127) full_path[p++] = icon->name[n++]; full_path[p]=0;
draw_image_icon(icon->x, icon->y, full_path);
@ -2100,7 +2117,7 @@ void wm_handle_right_click(int x, int y) {
process_create_elf("/bin/paint.elf", path);
} else if (str_ends_with(icon->name, ".md")) {
process_create_elf("/bin/markdown.elf", path);
} else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
} else if (is_image_file(icon->name)) {
process_create_elf("/bin/viewer.elf", path);
} else {
process_create_elf("/bin/txtedit.elf", path);

0
test.gif Normal file
View file