mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
New image decoder
This commit is contained in:
parent
032b154f41
commit
3fbcf3d4fd
43 changed files with 582 additions and 2754 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
32
Makefile
32
Makefile
|
|
@ -17,9 +17,7 @@ ISO_DIR = iso_root
|
||||||
KERNEL_ELF = $(BUILD_DIR)/boredos.elf
|
KERNEL_ELF = $(BUILD_DIR)/boredos.elf
|
||||||
ISO_IMAGE = boredos.iso
|
ISO_IMAGE = boredos.iso
|
||||||
|
|
||||||
# Exclude old network stack files
|
C_SOURCES = $(wildcard $(SRC_DIR)/*.c) \
|
||||||
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)) \
|
|
||||||
$(wildcard $(SRC_DIR)/lwip/core/*.c) \
|
$(wildcard $(SRC_DIR)/lwip/core/*.c) \
|
||||||
$(wildcard $(SRC_DIR)/lwip/core/ipv4/*.c) \
|
$(wildcard $(SRC_DIR)/lwip/core/ipv4/*.c) \
|
||||||
$(SRC_DIR)/lwip/netif/ethernet.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
|
NASMFLAGS = -f elf64
|
||||||
|
|
||||||
# Limine Version
|
|
||||||
LIMINE_VERSION = 10.8.2
|
LIMINE_VERSION = 10.8.2
|
||||||
LIMINE_URL_BASE = https://github.com/limine-bootloader/limine/raw/v$(LIMINE_VERSION)
|
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)
|
all: $(ISO_IMAGE)
|
||||||
|
|
||||||
# Ensure build directories exist
|
|
||||||
$(BUILD_DIR):
|
$(BUILD_DIR):
|
||||||
mkdir -p $(BUILD_DIR)
|
mkdir -p $(BUILD_DIR)
|
||||||
mkdir -p $(BUILD_DIR)
|
mkdir -p $(BUILD_DIR)
|
||||||
|
|
||||||
# Download Limine Binaries via Git
|
|
||||||
limine-setup:
|
limine-setup:
|
||||||
@if [ ! -f limine/limine-bios.sys ]; then \
|
@if [ ! -f limine/limine-bios.sys ]; then \
|
||||||
echo "Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary..."; \
|
echo "Limine binaries missing or invalid. Cloning v$(LIMINE_VERSION)-binary..."; \
|
||||||
|
|
@ -66,18 +61,15 @@ limine-setup:
|
||||||
@echo "Building Limine host utility..."; \
|
@echo "Building Limine host utility..."; \
|
||||||
$(MAKE) -C limine
|
$(MAKE) -C limine
|
||||||
|
|
||||||
# Compile C Sources
|
|
||||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup
|
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR) limine-setup
|
||||||
mkdir -p $(dir $@)
|
mkdir -p $(dir $@)
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Assemble ASM Sources
|
|
||||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR)
|
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.asm | $(BUILD_DIR)
|
||||||
$(NASM) $(NASMFLAGS) $< -o $@
|
$(NASM) $(NASMFLAGS) $< -o $@
|
||||||
|
|
||||||
# Assemble test files specifically if they get missed
|
|
||||||
$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/test_syscall.asm | $(BUILD_DIR)
|
$(BUILD_DIR)/test_syscall.o: $(SRC_DIR)/test_syscall.asm | $(BUILD_DIR)
|
||||||
$(NASM) $(NASMFLAGS) $< -o $@
|
$(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)
|
$(BUILD_DIR)/process_asm.o: $(SRC_DIR)/process_asm.asm | $(BUILD_DIR)
|
||||||
$(NASM) $(NASMFLAGS) $< -o $@
|
$(NASM) $(NASMFLAGS) $< -o $@
|
||||||
|
|
||||||
# Link Kernel
|
|
||||||
$(KERNEL_ELF): $(OBJ_FILES)
|
$(KERNEL_ELF): $(OBJ_FILES)
|
||||||
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
$(LD) $(LDFLAGS) -o $@ $(OBJ_FILES)
|
||||||
$(MAKE) -C $(SRC_DIR)/userland
|
$(MAKE) -C $(SRC_DIR)/userland
|
||||||
|
|
||||||
# Create ISO
|
|
||||||
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
$(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||||
rm -rf $(ISO_DIR)
|
rm -rf $(ISO_DIR)
|
||||||
mkdir -p $(ISO_DIR)
|
mkdir -p $(ISO_DIR)
|
||||||
mkdir -p $(ISO_DIR)/EFI/BOOT
|
mkdir -p $(ISO_DIR)/EFI/BOOT
|
||||||
|
|
||||||
# Copy Kernel and Config
|
|
||||||
cp $(KERNEL_ELF) $(ISO_DIR)/
|
cp $(KERNEL_ELF) $(ISO_DIR)/
|
||||||
# Build ISO limine.conf natively with modules
|
|
||||||
cp limine.conf $(ISO_DIR)/
|
cp limine.conf $(ISO_DIR)/
|
||||||
mkdir -p $(ISO_DIR)/bin
|
mkdir -p $(ISO_DIR)/bin
|
||||||
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
@for f in $(SRC_DIR)/userland/bin/*.elf; do \
|
||||||
|
|
@ -111,7 +99,6 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||||
fi \
|
fi \
|
||||||
done
|
done
|
||||||
|
|
||||||
# Copy README and WAD
|
|
||||||
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
@if [ -f README.md ]; then cp README.md $(ISO_DIR)/; fi
|
||||||
@if [ -f $(SRC_DIR)/userland/doom/doom1.wad ]; then \
|
@if [ -f $(SRC_DIR)/userland/doom/doom1.wad ]; then \
|
||||||
mkdir -p $(ISO_DIR)/Library/DOOM; \
|
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; \
|
echo " module_path: boot():/Library/DOOM/doom1.wad" >> $(ISO_DIR)/limine.conf; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy Wallpapers
|
|
||||||
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
mkdir -p $(ISO_DIR)/Library/images/Wallpapers
|
||||||
@for f in $(SRC_DIR)/images/wallpapers/*.jpg; do \
|
@for f in $(SRC_DIR)/images/wallpapers/*.jpg; do \
|
||||||
if [ -f "$$f" ]; then \
|
if [ -f "$$f" ]; then \
|
||||||
|
|
@ -130,16 +116,22 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||||
done
|
done
|
||||||
@if [ -f splash.jpg ]; then cp splash.jpg $(ISO_DIR)/; fi
|
@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.sys $(ISO_DIR)/
|
||||||
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
cp limine/limine-bios-cd.bin $(ISO_DIR)/
|
||||||
cp limine/limine-uefi-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/BOOTX64.EFI $(ISO_DIR)/EFI/BOOT/
|
||||||
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
cp limine/BOOTIA32.EFI $(ISO_DIR)/EFI/BOOT/
|
||||||
|
|
||||||
# Copy Fonts
|
|
||||||
mkdir -p $(ISO_DIR)/Library/Fonts
|
mkdir -p $(ISO_DIR)/Library/Fonts
|
||||||
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
@for f in $(SRC_DIR)/fonts/*.ttf; do \
|
||||||
if [ -f "$$f" ]; then \
|
if [ -f "$$f" ]; then \
|
||||||
|
|
@ -149,14 +141,12 @@ $(ISO_IMAGE): $(KERNEL_ELF) limine.conf limine-setup
|
||||||
fi \
|
fi \
|
||||||
done
|
done
|
||||||
|
|
||||||
# Generate ISO
|
$(XORRISO) -as mkisofs -R -J -b limine-bios-cd.bin \
|
||||||
$(XORRISO) -as mkisofs -b limine-bios-cd.bin \
|
|
||||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||||
--efi-boot limine-uefi-cd.bin \
|
--efi-boot limine-uefi-cd.bin \
|
||||||
-efi-boot-part --efi-boot-image --protective-msdos-label \
|
-efi-boot-part --efi-boot-image --protective-msdos-label \
|
||||||
$(ISO_DIR) -o $(ISO_IMAGE)
|
$(ISO_DIR) -o $(ISO_IMAGE)
|
||||||
|
|
||||||
# Install Limine to ISO (for BIOS boot)
|
|
||||||
./limine/limine bios-install $(ISO_IMAGE)
|
./limine/limine bios-install $(ISO_IMAGE)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
||||||
BIN
boredos.iso
BIN
boredos.iso
Binary file not shown.
BIN
build/cmd.o
BIN
build/cmd.o
Binary file not shown.
BIN
build/explorer.o
BIN
build/explorer.o
Binary file not shown.
BIN
build/idt.o
BIN
build/idt.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/nanojpeg.o
BIN
build/nanojpeg.o
Binary file not shown.
Binary file not shown.
BIN
build/ps2.o
BIN
build/ps2.o
Binary file not shown.
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
BIN
src/kernel/.DS_Store
vendored
BIN
src/kernel/.DS_Store
vendored
Binary file not shown.
137
src/kernel/dns.c
137
src/kernel/dns.c
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
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)
|
// Helper for label drawing (adapted from wm.c)
|
||||||
static void explorer_draw_icon_label(int x, int y, const char *label, uint32_t color) {
|
static void explorer_draw_icon_label(int x, int y, const char *label, uint32_t color) {
|
||||||
char line1[11] = {0}; // 10 chars + null
|
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);
|
process_create_elf("A:/bin/markdown.elf", path);
|
||||||
} else if (explorer_str_ends_with(path, ".pnt")) {
|
} else if (explorer_str_ends_with(path, ".pnt")) {
|
||||||
process_create_elf("A:/bin/paint.elf", path);
|
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);
|
process_create_elf("A:/bin/viewer.elf", path);
|
||||||
} else {
|
} else {
|
||||||
process_create_elf("A:/bin/txtedit.elf", path);
|
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 draw_icon(x + 5, y + 5, "");
|
||||||
} else if (explorer_str_ends_with(filename, ".pnt")) {
|
} else if (explorer_str_ends_with(filename, ".pnt")) {
|
||||||
draw_paint_icon(x + 5, y + 5, "");
|
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
|
// Build full path for thumbnail loading
|
||||||
char full_path[FAT32_MAX_PATH];
|
char full_path[FAT32_MAX_PATH];
|
||||||
explorer_strcpy(full_path, current_path);
|
explorer_strcpy(full_path, current_path);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
BIN
src/kernel/images/.DS_Store
vendored
BIN
src/kernel/images/.DS_Store
vendored
Binary file not shown.
BIN
src/kernel/images/gif/giphy-2.gif
Normal file
BIN
src/kernel/images/gif/giphy-2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
BIN
src/kernel/images/gif/giphy-3.gif
Normal file
BIN
src/kernel/images/gif/giphy-3.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/kernel/images/gif/giphy.gif
Normal file
BIN
src/kernel/images/gif/giphy.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
|
|
@ -41,6 +41,10 @@ isr%2_wrapper:
|
||||||
push r14
|
push r14
|
||||||
push r15
|
push r15
|
||||||
|
|
||||||
|
; Save SSE/FPU state
|
||||||
|
sub rsp, 512
|
||||||
|
fxsave [rsp]
|
||||||
|
|
||||||
; Pass current RSP as 1st argument (registers_t*)
|
; Pass current RSP as 1st argument (registers_t*)
|
||||||
mov rdi, rsp
|
mov rdi, rsp
|
||||||
|
|
||||||
|
|
@ -49,6 +53,10 @@ isr%2_wrapper:
|
||||||
; Update RSP with return value (task switch)
|
; Update RSP with return value (task switch)
|
||||||
mov rsp, rax
|
mov rsp, rax
|
||||||
|
|
||||||
|
; Restore SSE/FPU state
|
||||||
|
fxrstor [rsp]
|
||||||
|
add rsp, 512
|
||||||
|
|
||||||
pop r15
|
pop r15
|
||||||
pop r14
|
pop r14
|
||||||
pop r13
|
pop r13
|
||||||
|
|
@ -146,6 +154,10 @@ exception_common:
|
||||||
push r14
|
push r14
|
||||||
push r15
|
push r15
|
||||||
|
|
||||||
|
; Save SSE/FPU state
|
||||||
|
sub rsp, 512
|
||||||
|
fxsave [rsp]
|
||||||
|
|
||||||
; Pass current RSP as 1st argument (registers_t*)
|
; Pass current RSP as 1st argument (registers_t*)
|
||||||
mov rdi, rsp
|
mov rdi, rsp
|
||||||
|
|
||||||
|
|
@ -154,6 +166,10 @@ exception_common:
|
||||||
; Switch stack if needed (for process termination)
|
; Switch stack if needed (for process termination)
|
||||||
mov rsp, rax
|
mov rsp, rax
|
||||||
|
|
||||||
|
; Restore SSE/FPU state
|
||||||
|
fxrstor [rsp]
|
||||||
|
add rsp, 512
|
||||||
|
|
||||||
; Restore registers
|
; Restore registers
|
||||||
pop r15
|
pop r15
|
||||||
pop r14
|
pop r14
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ void kmain(void) {
|
||||||
fat32_mkdir("/Library");
|
fat32_mkdir("/Library");
|
||||||
fat32_mkdir("/Library/images");
|
fat32_mkdir("/Library/images");
|
||||||
fat32_mkdir("/Library/images/Wallpapers");
|
fat32_mkdir("/Library/images/Wallpapers");
|
||||||
|
fat32_mkdir("/Library/images/gif");
|
||||||
fat32_mkdir("/Library/Fonts");
|
fat32_mkdir("/Library/Fonts");
|
||||||
fat32_mkdir("/Library/DOOM");
|
fat32_mkdir("/Library/DOOM");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; // Y,Cb和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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,8 +31,7 @@ void process_init(void) {
|
||||||
kernel_proc->pml4_phys = paging_get_pml4_phys();
|
kernel_proc->pml4_phys = paging_get_pml4_phys();
|
||||||
kernel_proc->kernel_stack = 0;
|
kernel_proc->kernel_stack = 0;
|
||||||
|
|
||||||
// Initialize FPU state for kernel
|
// Initialize FPU/SSE state for kernel (first interrupt will capture it on stack)
|
||||||
asm volatile("fxsave %0" : "=m"(kernel_proc->fpu_state));
|
|
||||||
kernel_proc->fpu_initialized = true;
|
kernel_proc->fpu_initialized = true;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL;
|
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;
|
if (!new_proc->pml4_phys) return;
|
||||||
|
|
||||||
// 2. Allocate aligned stack
|
// 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
|
void* kernel_stack = kmalloc_aligned(32768, 32768); // Needed for when user interrupts to Ring 0
|
||||||
|
|
||||||
if (is_user) {
|
if (is_user) {
|
||||||
// Map user stack to 0x800000
|
// 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
|
// Allocate code page aligned and copy code
|
||||||
void* code = kmalloc_aligned(4096, 4096);
|
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)
|
// Push 15 zeros for general purpose registers (r15 -> rax)
|
||||||
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
|
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->kernel_stack = (uint64_t)kernel_stack + 32768;
|
||||||
new_proc->rsp = (uint64_t)stack_ptr;
|
new_proc->rsp = (uint64_t)stack_ptr;
|
||||||
} else {
|
} else {
|
||||||
// Kernel thread
|
// 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) = 0x10; // SS (Kernel Data)
|
||||||
stack_ptr--;
|
stack_ptr--;
|
||||||
*stack_ptr = (uint64_t)stack_ptr; // RSP
|
*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; // int_no
|
||||||
*(--stack_ptr) = 0; // err_code
|
*(--stack_ptr) = 0; // err_code
|
||||||
|
|
||||||
|
// Push 15 zeros for general purpose registers (r15 -> rax)
|
||||||
for (int i = 0; i < 15; i++) *(--stack_ptr) = 0;
|
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;
|
new_proc->rsp = (uint64_t)stack_ptr;
|
||||||
|
kfree(user_stack); // Unused for kernel threads
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize FPU state for new process
|
// Initialize FPU state for new process
|
||||||
asm volatile("fninit");
|
asm volatile("fninit");
|
||||||
asm volatile("fxsave %0" : "=m"(new_proc->fpu_state));
|
|
||||||
new_proc->fpu_initialized = true;
|
new_proc->fpu_initialized = true;
|
||||||
|
|
||||||
// Add to linked list
|
// 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) = entry_point; // RIP
|
||||||
*(--stack_ptr) = 0; // err_code
|
*(--stack_ptr) = 0; // err_code
|
||||||
*(--stack_ptr) = 0; // int_no
|
*(--stack_ptr) = 0; // int_no
|
||||||
|
|
||||||
// 15 General purpose registers
|
// 15 General purpose registers
|
||||||
*(--stack_ptr) = 0; // RAX
|
*(--stack_ptr) = 0; // RAX
|
||||||
*(--stack_ptr) = 0; // RBX
|
*(--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; // R14
|
||||||
*(--stack_ptr) = 0; // R15
|
*(--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 = (uint64_t)kernel_stack + 32768;
|
||||||
new_proc->kernel_stack_alloc = kernel_stack;
|
new_proc->kernel_stack_alloc = kernel_stack;
|
||||||
new_proc->user_stack_alloc = 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
|
// Initialize FPU state for new process
|
||||||
asm volatile("fninit");
|
asm volatile("fninit");
|
||||||
asm volatile("fxsave %0" : "=m"(new_proc->fpu_state));
|
|
||||||
new_proc->fpu_initialized = true;
|
new_proc->fpu_initialized = true;
|
||||||
|
|
||||||
// Slot is already counted in process_count if new, or reused.
|
// 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");
|
// serial_write("SCHED\n");
|
||||||
|
|
||||||
// Save context
|
// Save/Restore context
|
||||||
current_process->rsp = current_rsp;
|
current_process->rsp = current_rsp;
|
||||||
|
|
||||||
// Save FPU state
|
|
||||||
if (current_process->fpu_initialized) {
|
|
||||||
asm volatile("fxsave %0" : "=m"(current_process->fpu_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch process
|
// Switch process
|
||||||
current_process = current_process->next;
|
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
|
// Update Kernel Stack for User Mode interrupts and System Calls
|
||||||
if (current_process->is_user && current_process->kernel_stack) {
|
if (current_process->is_user && current_process->kernel_stack) {
|
||||||
tss_set_stack(current_process->kernel_stack);
|
tss_set_stack(current_process->kernel_stack);
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@ struct FAT32_FileHandle;
|
||||||
|
|
||||||
// Registers saved on the stack by interrupts/exceptions
|
// Registers saved on the stack by interrupts/exceptions
|
||||||
typedef struct registers_t {
|
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 r15, r14, r13, r12, r11, r10, r9, r8;
|
||||||
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
|
uint64_t rbp, rdi, rsi, rdx, rcx, rbx, rax;
|
||||||
uint64_t int_no, err_code;
|
uint64_t int_no, err_code;
|
||||||
uint64_t rip, cs, rflags, rsp, ss;
|
uint64_t rip, cs, rflags, rsp, ss;
|
||||||
} __attribute__((packed)) registers_t;
|
} __attribute__((packed, aligned(16))) registers_t;
|
||||||
|
|
||||||
typedef struct process {
|
typedef struct process {
|
||||||
uint32_t pid;
|
uint32_t pid;
|
||||||
|
|
@ -45,7 +46,6 @@ typedef struct process {
|
||||||
|
|
||||||
struct process *next;
|
struct process *next;
|
||||||
|
|
||||||
uint8_t fpu_state[512] __attribute__((aligned(16)));
|
|
||||||
bool fpu_initialized;
|
bool fpu_initialized;
|
||||||
} __attribute__((aligned(16))) process_t;
|
} __attribute__((aligned(16))) process_t;
|
||||||
|
|
||||||
|
|
|
||||||
29
src/kernel/stb_image.c
Normal file
29
src/kernel/stb_image.c
Normal 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"
|
||||||
|
|
@ -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 *dest = &win->pixels[(ry + y) * win->w + rx];
|
||||||
uint32_t *src = &image_data[(src_y_offset + y) * (int)params[2] + src_x_offset];
|
uint32_t *src = &image_data[(src_y_offset + y) * (int)params[2] + src_x_offset];
|
||||||
for (int x = 0; x < rw; x++) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ syscall_entry:
|
||||||
push r14
|
push r14
|
||||||
push r15
|
push r15
|
||||||
|
|
||||||
|
; Save SSE/FPU state
|
||||||
|
sub rsp, 512
|
||||||
|
fxsave [rsp]
|
||||||
|
|
||||||
; 4. Call C handler with registers_t*
|
; 4. Call C handler with registers_t*
|
||||||
mov rdi, rsp
|
mov rdi, rsp
|
||||||
sti
|
sti
|
||||||
|
|
@ -55,6 +59,10 @@ syscall_entry:
|
||||||
; 5. Switch to the resulting RSP (might be different if task switched)
|
; 5. Switch to the resulting RSP (might be different if task switched)
|
||||||
mov rsp, rax
|
mov rsp, rax
|
||||||
|
|
||||||
|
; Restore SSE/FPU state
|
||||||
|
fxrstor [rsp]
|
||||||
|
add rsp, 512
|
||||||
|
|
||||||
; 6. Restore and return via iretq
|
; 6. Restore and return via iretq
|
||||||
pop r15
|
pop r15
|
||||||
pop r14
|
pop r14
|
||||||
|
|
|
||||||
207
src/kernel/tcp.c
207
src/kernel/tcp.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,7 @@ BIN_DIR = bin
|
||||||
LIBC_SOURCES = $(wildcard libc/*.c)
|
LIBC_SOURCES = $(wildcard libc/*.c)
|
||||||
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o
|
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))
|
APP_ELFS = $(patsubst %.c, $(BIN_DIR)/%.elf, $(APP_SOURCES))
|
||||||
|
|
||||||
DOOM_SOURCES = $(wildcard doom/*.c)
|
DOOM_SOURCES = $(wildcard doom/*.c)
|
||||||
|
|
@ -30,25 +30,25 @@ $(BIN_DIR)/crt0.o: crt0.asm
|
||||||
$(BIN_DIR)/%.o: libc/%.c
|
$(BIN_DIR)/%.o: libc/%.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/nanojpeg.o: nanojpeg.c
|
$(BIN_DIR)/stb_image.o: stb_image.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/%.o: %.c
|
$(BIN_DIR)/%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(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 $@
|
$(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 $@
|
$(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 $@
|
$(LD) $(LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/%.o: doom/%.c
|
$(BIN_DIR)/%.o: doom/%.c
|
||||||
$(CC) $(CFLAGS) -Wno-error -Idoom -c $< -o $@
|
$(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 $@
|
$(LD) $(LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o
|
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||||
#include "libc/syscall.h"
|
#include "libc/syscall.h"
|
||||||
#include "libc/libui.h"
|
#include "libc/libui.h"
|
||||||
#include "nanojpeg.h"
|
#include "stb_image.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -110,6 +110,13 @@ typedef struct {
|
||||||
float scale;
|
float scale;
|
||||||
int list_depth;
|
int list_depth;
|
||||||
int blockquote_depth; // Added blockquote support
|
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;
|
} RenderElement;
|
||||||
|
|
||||||
#define MAX_ELEMENTS 65536
|
#define MAX_ELEMENTS 65536
|
||||||
|
|
@ -129,18 +136,40 @@ static int focused_element = -1;
|
||||||
|
|
||||||
static void parse_html(const char *html);
|
static void parse_html(const char *html);
|
||||||
static void parse_html_incremental(const char *html, int safe_len);
|
static void parse_html_incremental(const char *html, int safe_len);
|
||||||
|
static void browser_reflow(void);
|
||||||
static void browser_paint(void);
|
static void browser_paint(void);
|
||||||
static int inc_parse_offset = 0;
|
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) {
|
static void browser_clear(void) {
|
||||||
for (int i = 0; i < element_count; i++) {
|
for (int i = 0; i < element_count; i++) {
|
||||||
if (elements[i].img_pixels) {
|
if (elements[i].img_pixels) {
|
||||||
free(elements[i].img_pixels);
|
free(elements[i].img_pixels);
|
||||||
elements[i].img_pixels = NULL;
|
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;
|
element_count = 0;
|
||||||
total_content_height = 0;
|
total_content_height = 0;
|
||||||
|
inc_font_ptr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool str_iequals(const char *s1, const char *s2) {
|
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 (amp_pos != -1) safe_len = amp_pos;
|
||||||
if (safe_len > inc_parse_offset) {
|
if (safe_len > inc_parse_offset) {
|
||||||
parse_html_incremental(body, safe_len);
|
parse_html_incremental(body, safe_len);
|
||||||
|
browser_reflow();
|
||||||
browser_paint();
|
browser_paint();
|
||||||
ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
|
ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
|
||||||
last_render = total;
|
last_render = total;
|
||||||
|
|
@ -288,30 +318,71 @@ static int fetch_content(const char *url, char *dest_buf, int max_len, bool prog
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decode_jpeg(unsigned char *data, int len, RenderElement *el) {
|
static void decode_image(unsigned char *data, int len, RenderElement *el) {
|
||||||
njInit();
|
int img_w_orig, img_h_orig, channels;
|
||||||
if (njDecode(data, len) == NJ_OK) {
|
int frame_count = 1;
|
||||||
int img_w_orig = njGetWidth(); int img_h_orig = njGetHeight();
|
int *delays = NULL;
|
||||||
unsigned char *rgb = njGetImage();
|
unsigned char *rgba = NULL;
|
||||||
if (rgb) {
|
|
||||||
int fit_w = img_w_orig; int fit_h = img_h_orig;
|
if (len > 4 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F') {
|
||||||
if (fit_w > win_w - 60) { fit_h = fit_h * (win_w - 60) / fit_w; fit_w = win_w - 60; }
|
rgba = stbi_load_gif_from_memory(data, len, &delays, &img_w_orig, &img_h_orig, &frame_count, &channels, 4);
|
||||||
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
|
} 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));
|
el->img_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
|
||||||
if (el->img_pixels) {
|
if (el->img_pixels) {
|
||||||
for (int y = 0; y < fit_h; y++) {
|
for (int y = 0; y < fit_h; y++) {
|
||||||
int sy = y * img_h_orig / fit_h;
|
int sy = y * img_h_orig / fit_h;
|
||||||
for (int x = 0; x < fit_w; x++) {
|
for (int x = 0; x < fit_w; x++) {
|
||||||
int sx = x * img_w_orig / fit_w;
|
int sx = x * img_w_orig / fit_w;
|
||||||
int idx = (sy * img_w_orig + sx) * 3;
|
int idx = (sy * img_w_orig + sx) * 4;
|
||||||
el->img_pixels[y * fit_w + x] = 0xFF000000 | (rgb[idx] << 16) | (rgb[idx+1] << 8) | rgb[idx+2];
|
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;
|
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) {
|
static int decode_chunked_bin(char *body, int total_len) {
|
||||||
|
|
@ -347,6 +418,11 @@ static void load_image(RenderElement *el) {
|
||||||
char *u = url;
|
char *u = url;
|
||||||
const char *s = "http://"; while(*s) *u++ = *s++;
|
const char *s = "http://"; while(*s) *u++ = *s++;
|
||||||
s = current_host; 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++ = '/';
|
if (el->attr_value[0] != '/') *u++ = '/';
|
||||||
s = el->attr_value; while(*s) *u++ = *s++;
|
s = el->attr_value; while(*s) *u++ = *s++;
|
||||||
*u = 0;
|
*u = 0;
|
||||||
|
|
@ -361,8 +437,14 @@ static void load_image(RenderElement *el) {
|
||||||
if (strstr(img_resp, "Transfer-Encoding: chunked")) {
|
if (strstr(img_resp, "Transfer-Encoding: chunked")) {
|
||||||
body_len = decode_chunked_bin(body, body_len);
|
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];
|
static int line_elements[512];
|
||||||
|
|
@ -589,9 +671,15 @@ static void parse_html(const char *html) {
|
||||||
browser_clear();
|
browser_clear();
|
||||||
list_depth = 0;
|
list_depth = 0;
|
||||||
cur_line_y = 10; cur_line_x = 10; line_element_count = 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] = "";
|
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;
|
||||||
#define EFF_CENTER ((center_depth > 0) && (table_depth == 0))
|
uint32_t current_color = COLOR_TEXT;
|
||||||
|
char current_link[256] = "";
|
||||||
float current_scale = 15.0f; float base_scale = 15.0f;
|
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;
|
bool is_space_pending = false;
|
||||||
char current_form_action[256] = ""; int current_form_id = 0;
|
char current_form_action[256] = ""; int current_form_id = 0;
|
||||||
bool skip_content = false;
|
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, "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, "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, "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, "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")) {
|
else if (str_iequals(tag_name+1, "title")) {
|
||||||
inside_title = false;
|
inside_title = false;
|
||||||
|
|
@ -663,7 +760,7 @@ static void parse_html(const char *html) {
|
||||||
else if (str_iequals(tag_name, "dd")) {
|
else if (str_iequals(tag_name, "dd")) {
|
||||||
emit_br();
|
emit_br();
|
||||||
RenderElement *el = &elements[element_count++];
|
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->tag = TAG_NONE;
|
||||||
el->content[0] = ' '; el->content[1] = ' '; el->content[2] = ' '; el->content[3] = ' '; el->content[4] = 0;
|
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->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, "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, "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, "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, "address")) { emit_br(); }
|
||||||
else if (str_iequals(tag_name, "html") || str_iequals(tag_name, "body")) skip_content = false;
|
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;
|
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;
|
current_scale = base_scale;
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "font")) {
|
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=\"");
|
char *color_str = str_istrstr(attr_buf, "color=\"");
|
||||||
if (color_str) {
|
if (color_str) {
|
||||||
current_color = parse_html_color(color_str + 7);
|
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")) {
|
else if (str_iequals(tag_name, "li")) {
|
||||||
emit_br();
|
emit_br();
|
||||||
RenderElement *el = &elements[element_count++];
|
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->tag = TAG_NONE;
|
||||||
|
|
||||||
if (list_depth > 0 && list_type[list_depth - 1] == 1) { // OL
|
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")) {
|
} else if (str_iequals(tag_name, "hr")) {
|
||||||
emit_br();
|
emit_br();
|
||||||
RenderElement *el = &elements[element_count++];
|
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->tag = TAG_HR;
|
||||||
el->list_depth = list_depth;
|
el->list_depth = list_depth;
|
||||||
el->blockquote_depth = blockquote_depth; // Set blockquote depth
|
el->blockquote_depth = blockquote_depth; // Set blockquote depth
|
||||||
|
|
@ -792,19 +901,19 @@ static void parse_html(const char *html) {
|
||||||
emit_br();
|
emit_br();
|
||||||
} else if (str_iequals(tag_name, "img")) {
|
} else if (str_iequals(tag_name, "img")) {
|
||||||
RenderElement *el = &elements[element_count++];
|
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;
|
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = EFF_CENTER;
|
||||||
char *src = str_istrstr(attr_buf, "src=\"");
|
char *src = str_istrstr(attr_buf, "src=\"");
|
||||||
if (src) {
|
if (src) {
|
||||||
src += 5; int l = 0;
|
src += 5; int l = 0;
|
||||||
while(src[l] && src[l] != '\"' && l < 255) { el->attr_value[l] = src[l]; l++; }
|
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; }
|
if (el->img_pixels) { el->w = el->img_w; el->h = el->img_h; }
|
||||||
el->blockquote_depth = blockquote_depth; // Set blockquote depth
|
el->blockquote_depth = blockquote_depth; // Set blockquote depth
|
||||||
} else if (str_iequals(tag_name, "input")) {
|
} else if (str_iequals(tag_name, "input")) {
|
||||||
RenderElement *el = &elements[element_count++];
|
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;
|
el->tag = TAG_INPUT; el->w = 160; el->h = 20; el->centered = EFF_CENTER;
|
||||||
char *val = str_istrstr(attr_buf, "value=\"");
|
char *val = str_istrstr(attr_buf, "value=\"");
|
||||||
char *ph = str_istrstr(attr_buf, "placeholder=\"");
|
char *ph = str_istrstr(attr_buf, "placeholder=\"");
|
||||||
|
|
@ -858,7 +967,7 @@ static void parse_html(const char *html) {
|
||||||
} else {
|
} else {
|
||||||
int word_w = ui_get_string_width_scaled(word, current_scale);
|
int word_w = ui_get_string_width_scaled(word, current_scale);
|
||||||
RenderElement *el = &elements[element_count++];
|
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;
|
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->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->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color;
|
||||||
|
|
@ -884,7 +993,7 @@ static void parse_html(const char *html) {
|
||||||
} else {
|
} else {
|
||||||
int word_w = ui_get_string_width_scaled(word, current_scale);
|
int word_w = ui_get_string_width_scaled(word, current_scale);
|
||||||
RenderElement *el = &elements[element_count++];
|
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;
|
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->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->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;
|
is_space_pending = false;
|
||||||
if (element_count < MAX_ELEMENTS && !inside_title) {
|
if (element_count < MAX_ELEMENTS && !inside_title) {
|
||||||
RenderElement *el = &elements[element_count++];
|
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->tag = TAG_NONE; el->content[0] = ' '; el->content[1] = 0;
|
||||||
el->w = ui_get_string_width_scaled(" ", current_scale);
|
el->w = ui_get_string_width_scaled(" ", current_scale);
|
||||||
el->h = ui_get_font_height_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) {
|
if (element_count < MAX_ELEMENTS) {
|
||||||
int word_w = ui_get_string_width_scaled(word, current_scale);
|
int word_w = ui_get_string_width_scaled(word, current_scale);
|
||||||
RenderElement *el = &elements[element_count++];
|
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;
|
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->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->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, "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, "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, "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, "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")) {
|
else if (str_iequals(tag_name+1, "title")) {
|
||||||
inside_title = false;
|
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, "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, "dt")) { emit_br(); is_bold = true; }
|
||||||
else if (str_iequals(tag_name, "dd")) {
|
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->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;
|
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, "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, "u")) is_underline = true;
|
||||||
else if (str_iequals(tag_name, "address")) emit_br();
|
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') {
|
else if (tag_name[0] == 'h' && tag_name[1] >= '1' && tag_name[1] <= '6') {
|
||||||
emit_br(); emit_br(); is_bold = true;
|
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;
|
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;
|
current_scale = base_scale;
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "font")) {
|
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=\"");
|
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); }
|
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; }
|
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, "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, "pre")) { emit_br(); is_pre = true; current_scale = 14.0f; }
|
||||||
else if (str_iequals(tag_name, "li")) {
|
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; }
|
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; }
|
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;
|
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, "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")) {
|
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;
|
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); }
|
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; }
|
||||||
if (el->img_pixels) { el->w = el->img_w; el->h = el->img_h; } el->blockquote_depth = blockquote_depth;
|
el->blockquote_depth = blockquote_depth;
|
||||||
}
|
}
|
||||||
else if (str_iequals(tag_name, "input")) {
|
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;
|
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 (html[i] == '\n' || html[i] == '\r') {
|
||||||
if (w_idx > 0) {
|
if (w_idx > 0) {
|
||||||
word[w_idx] = 0; decode_html_entities(word); int word_w = ui_get_string_width_scaled(word, current_scale);
|
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;
|
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;
|
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; }
|
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) {
|
if (w_idx > 0) {
|
||||||
word[w_idx] = 0; decode_html_entities(word); int word_w = ui_get_string_width_scaled(word, current_scale);
|
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;
|
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;
|
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; }
|
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) {
|
if (is_space_pending) {
|
||||||
is_space_pending = false;
|
is_space_pending = false;
|
||||||
if (element_count < MAX_ELEMENTS) {
|
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;
|
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;
|
int draw_y = el->y - scroll_y + URL_BAR_H;
|
||||||
if (draw_y < URL_BAR_H - 400 || draw_y > win_h) continue;
|
if (draw_y < URL_BAR_H - 400 || draw_y > win_h) continue;
|
||||||
if (el->tag == TAG_IMG) {
|
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 ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
|
||||||
} else if (el->tag == TAG_INPUT) {
|
} else if (el->tag == TAG_INPUT) {
|
||||||
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF);
|
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, el->w, 2, 0xFF888888);
|
||||||
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
ui_draw_rect(win_browser, el->x, draw_y + (el->h / 2) + 2, el->w, 1, 0xFFFFFFFF);
|
||||||
} else if (el->tag == TAG_NONE) {
|
} else if (el->tag == TAG_NONE) {
|
||||||
ui_draw_string_scaled(win_browser, el->x, draw_y, el->content, el->color, el->scale);
|
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) {
|
if (el->bold) {
|
||||||
ui_draw_string_scaled(win_browser, el->x + 1, draw_y, el->content, el->color, el->scale);
|
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);
|
browser_reflow(); browser_paint(); ui_mark_dirty(win_browser, 0, 0, win_w, win_h);
|
||||||
needs_repaint = false;
|
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++);
|
for(volatile int x=0; x<10000; x++);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
6
src/kernel/userland/libc/string.h
Normal file
6
src/kernel/userland/libc/string.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef BOREDOS_LIBC_STRING_H
|
||||||
|
#define BOREDOS_LIBC_STRING_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -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; // Y,Cb和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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include "libc/syscall.h"
|
#include "libc/syscall.h"
|
||||||
#include "libc/libui.h"
|
#include "libc/libui.h"
|
||||||
#include "libc/stdlib.h"
|
#include "libc/stdlib.h"
|
||||||
#include "nanojpeg.h"
|
#include "stb_image.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.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++) {
|
for (int y = 0; y < dst_h; y++) {
|
||||||
int src_y = y * src_h / dst_h;
|
int src_y = y * src_h / dst_h;
|
||||||
for (int x = 0; x < dst_w; x++) {
|
for (int x = 0; x < dst_w; x++) {
|
||||||
int src_x = x * src_w / dst_w;
|
int src_x = x * src_w / dst_w;
|
||||||
int idx = (src_y * src_w + src_x) * 3;
|
int idx = (src_y * src_w + src_x) * 4;
|
||||||
dst[y * dst_w + x] = 0xFF000000 | (rgb[idx] << 16) | (rgb[idx + 1] << 8) | rgb[idx + 2];
|
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);
|
unsigned char *buf = (unsigned char *)malloc(size);
|
||||||
if (buf) {
|
if (buf) {
|
||||||
sys_read(fd, buf, size);
|
sys_read(fd, buf, size);
|
||||||
njInit();
|
int img_w, img_h, channels;
|
||||||
if (njDecode(buf, size) == NJ_OK) {
|
unsigned char *img = stbi_load_from_memory(buf, size, &img_w, &img_h, &channels, 4);
|
||||||
scale_rgb_to_argb(njGetImage(), njGetWidth(), njGetHeight(), wp->thumb, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H);
|
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;
|
wp->valid = 1;
|
||||||
|
stbi_image_free(img);
|
||||||
}
|
}
|
||||||
njDone();
|
|
||||||
free(buf); // Release memory
|
free(buf); // Release memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/kernel/userland/stb_image.c
Normal file
5
src/kernel/userland/stb_image.c
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// stb_image.c wrapper for Userland Mode
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
@ -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
|
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
|
||||||
no warranty implied; use at your own risk
|
no warranty implied; use at your own risk
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||||
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
// 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.
|
// 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/syscall.h"
|
||||||
#include "libc/libui.h"
|
#include "libc/libui.h"
|
||||||
#include "libc/stdlib.h"
|
#include "libc/stdlib.h"
|
||||||
|
|
@ -13,6 +13,12 @@
|
||||||
#define VIEWER_MAX_H 600
|
#define VIEWER_MAX_H 600
|
||||||
|
|
||||||
static uint32_t *viewer_pixels = NULL;
|
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_w = 0;
|
||||||
static int viewer_img_h = 0;
|
static int viewer_img_h = 0;
|
||||||
static char viewer_title[64] = "Viewer";
|
static char viewer_title[64] = "Viewer";
|
||||||
|
|
@ -33,20 +39,43 @@ static int viewer_strlen(const char *s) {
|
||||||
return len;
|
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) {
|
uint32_t *dst, int dst_w, int dst_h) {
|
||||||
for (int y = 0; y < dst_h; y++) {
|
if (src_w == dst_w && src_h == dst_h) {
|
||||||
int src_y = y * src_h / dst_h;
|
// Fast path: 1:1 copy
|
||||||
if (src_y >= src_h) src_y = src_h - 1;
|
for (int i = 0; i < dst_w * dst_h; i++) {
|
||||||
for (int x = 0; x < dst_w; x++) {
|
int idx = i * 4;
|
||||||
int src_x = x * src_w / dst_w;
|
dst[i] = ((uint32_t)rgba[idx + 3] << 24) | ((uint32_t)rgba[idx] << 16) |
|
||||||
if (src_x >= src_w) src_x = src_w - 1;
|
((uint32_t)rgba[idx + 1] << 8) | rgba[idx + 2];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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 cw = win_w - 8;
|
||||||
int ch = win_h - 28;
|
int ch = win_h - 28;
|
||||||
|
|
||||||
ui_draw_rect(win, cx, cy, cw, ch, 0xFF1A1A1A);
|
|
||||||
|
|
||||||
if (!viewer_has_image) {
|
if (!viewer_has_image) {
|
||||||
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
ui_draw_string(win, cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t *pixels = viewer_pixels;
|
||||||
|
if (viewer_frames) pixels = viewer_frames[viewer_current_frame];
|
||||||
|
|
||||||
int disp_w = viewer_img_w;
|
int disp_w = viewer_img_w;
|
||||||
int disp_h = viewer_img_h;
|
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 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));
|
uint32_t *temp_buf = malloc(disp_w * disp_h * sizeof(uint32_t));
|
||||||
if (temp_buf) {
|
if (temp_buf) {
|
||||||
for (int y = 0; y < disp_h; y++) {
|
if (disp_w == viewer_img_w && disp_h == viewer_img_h) {
|
||||||
int src_y = y * viewer_img_h / disp_h;
|
// Fast path: 1:1
|
||||||
if (src_y >= viewer_img_h) src_y = viewer_img_h - 1;
|
for (int i = 0; i < disp_w * disp_h; i++) temp_buf[i] = pixels[i];
|
||||||
for (int x = 0; x < disp_w; x++) {
|
} else {
|
||||||
int src_x = x * viewer_img_w / disp_w;
|
// Fixed-point 16.16
|
||||||
if (src_x >= viewer_img_w) src_x = viewer_img_w - 1;
|
uint32_t step_x = (viewer_img_w << 16) / disp_w;
|
||||||
temp_buf[y * disp_w + x] = viewer_pixels[src_y * viewer_img_w + src_x];
|
uint32_t step_y = (viewer_img_h << 16) / disp_h;
|
||||||
|
uint32_t curr_y = 0;
|
||||||
|
|
||||||
|
for (int y = 0; y < disp_h; y++) {
|
||||||
|
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++) {
|
||||||
|
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);
|
ui_draw_image(win, ox, oy, disp_w, disp_h, temp_buf);
|
||||||
free(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) {
|
void viewer_open_file(const char *path) {
|
||||||
int fd = sys_open(path, "r");
|
int fd = sys_open(path, "r");
|
||||||
if (fd < 0) return;
|
if (fd < 0) return;
|
||||||
|
|
||||||
// We can't use stat yet, so read chunks until EOF
|
uint32_t file_size = sys_size(fd);
|
||||||
// Alternatively, use a large buffer if sys_read handles large chunks.
|
if (file_size == 0 || file_size > 32 * 1024 * 1024) { // 32MB limit
|
||||||
int alloc_size = 2 * 1024 * 1024;
|
sys_close(fd);
|
||||||
unsigned char *buf = malloc(alloc_size);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *buf = malloc(file_size);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
sys_close(fd);
|
sys_close(fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int total_read = 0;
|
int total_read = 0;
|
||||||
while (total_read < alloc_size) {
|
while (total_read < (int)file_size) {
|
||||||
int chunk = sys_read(fd, (char*)buf + total_read, alloc_size - total_read);
|
int chunk = sys_read(fd, (char*)buf + total_read, (int)file_size - total_read);
|
||||||
if (chunk <= 0) break;
|
if (chunk <= 0) break;
|
||||||
total_read += chunk;
|
total_read += chunk;
|
||||||
}
|
}
|
||||||
|
|
@ -142,20 +169,35 @@ void viewer_open_file(const char *path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
njInit();
|
// Free previous image if any
|
||||||
nj_result_t result = njDecode(buf, total_read);
|
if (viewer_pixels) { free(viewer_pixels); viewer_pixels = NULL; }
|
||||||
if (result != NJ_OK) {
|
if (viewer_frames) {
|
||||||
njDone();
|
for (int i = 0; i < viewer_frame_count; i++) {
|
||||||
free(buf);
|
if (viewer_frames[i]) free(viewer_frames[i]);
|
||||||
return;
|
}
|
||||||
|
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();
|
if (!rgba || img_w <= 0 || img_h <= 0) {
|
||||||
int img_h = njGetHeight();
|
if (rgba) stbi_image_free(rgba);
|
||||||
unsigned char *rgb = njGetImage();
|
|
||||||
|
|
||||||
if (!rgb || img_w <= 0 || img_h <= 0) {
|
|
||||||
njDone();
|
|
||||||
free(buf);
|
free(buf);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -171,15 +213,41 @@ void viewer_open_file(const char *path) {
|
||||||
fit_h = VIEWER_MAX_H;
|
fit_h = VIEWER_MAX_H;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewer_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
|
if (frame_count > 1 && delays) {
|
||||||
if (viewer_pixels) {
|
viewer_frames = malloc(frame_count * sizeof(uint32_t *));
|
||||||
viewer_scale_rgb_to_argb(rgb, img_w, img_h, viewer_pixels, fit_w, fit_h);
|
viewer_delays = malloc(frame_count * sizeof(int));
|
||||||
viewer_img_w = fit_w;
|
if (viewer_frames && viewer_delays) {
|
||||||
viewer_img_h = fit_h;
|
viewer_frame_count = frame_count;
|
||||||
viewer_has_image = true;
|
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_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);
|
free(buf);
|
||||||
|
|
||||||
viewer_strcpy(viewer_file_path, path);
|
viewer_strcpy(viewer_file_path, path);
|
||||||
|
|
@ -204,7 +272,7 @@ void viewer_open_file(const char *path) {
|
||||||
|
|
||||||
win_w = fit_w + 16;
|
win_w = fit_w + 16;
|
||||||
if (win_w < 200) win_w = 200;
|
if (win_w < 200) win_w = 200;
|
||||||
win_h = fit_h + 64;
|
win_h = fit_h + 34;
|
||||||
if (win_h < 100) win_h = 100;
|
if (win_h < 100) win_h = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,10 +291,23 @@ int main(int argc, char **argv) {
|
||||||
viewer_paint(win);
|
viewer_paint(win);
|
||||||
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
ui_mark_dirty(win, 0, 0, win_w, win_h - 20);
|
||||||
} else if (ev.type == GUI_EVENT_CLICK) {
|
} 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) {
|
} else if (ev.type == GUI_EVENT_CLOSE) {
|
||||||
sys_exit(0);
|
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;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "wallpaper.h"
|
#include "wallpaper.h"
|
||||||
#include "nanojpeg.h"
|
#define STBI_NO_STDIO
|
||||||
|
#include "userland/stb_image.h"
|
||||||
#include "graphics.h"
|
#include "graphics.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include "memory_manager.h"
|
#include "memory_manager.h"
|
||||||
|
|
@ -10,7 +11,7 @@
|
||||||
// Static buffer for the current wallpaper (max 1920x1080)
|
// Static buffer for the current wallpaper (max 1920x1080)
|
||||||
#define MAX_WP_WIDTH 1920
|
#define MAX_WP_WIDTH 1920
|
||||||
#define MAX_WP_HEIGHT 1080
|
#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_width = 0;
|
||||||
static int wp_height = 0;
|
static int wp_height = 0;
|
||||||
|
|
||||||
|
|
@ -18,39 +19,33 @@ static int wp_height = 0;
|
||||||
static volatile const char *pending_wallpaper_path = NULL;
|
static volatile const char *pending_wallpaper_path = NULL;
|
||||||
static char pending_path_buf[256];
|
static char pending_path_buf[256];
|
||||||
|
|
||||||
// Simple nearest-neighbor scale from decoded RGB to ARGB pixel buffer
|
// Simple nearest-neighbor scale from decoded RGBA to ARGB pixel buffer
|
||||||
static void scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_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) {
|
uint32_t *dst, int dst_w, int dst_h) {
|
||||||
for (int y = 0; y < dst_h; y++) {
|
for (int y = 0; y < dst_h; y++) {
|
||||||
int src_y = y * src_h / dst_h;
|
int src_y = y * src_h / dst_h;
|
||||||
if (src_y >= src_h) src_y = src_h - 1;
|
if (src_y >= src_h) src_y = src_h - 1;
|
||||||
for (int x = 0; x < dst_w; x++) {
|
for (int x = 0; x < dst_w; x++) {
|
||||||
int src_x = x * src_w / dst_w;
|
int src_x = x * src_w / dst_w;
|
||||||
if (src_x >= src_w) src_x = src_w - 1;
|
if (src_x >= src_w) src_x = src_w - 1;
|
||||||
int idx = (src_y * src_w + src_x) * 3;
|
|
||||||
unsigned char r = rgb[idx];
|
size_t idx = ((size_t)src_y * (size_t)src_w + (size_t)src_x) * 4;
|
||||||
unsigned char g = rgb[idx + 1];
|
unsigned char r = rgba[idx];
|
||||||
unsigned char b = rgb[idx + 2];
|
unsigned char g = rgba[idx + 1];
|
||||||
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
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)
|
// 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) {
|
static int decode_and_set_wallpaper(const unsigned char *jpg_data, unsigned int jpg_size) {
|
||||||
njInit();
|
int img_w, img_h, channels;
|
||||||
nj_result_t result = njDecode(jpg_data, (int)jpg_size);
|
// We request 4 channels (RGBA) for better alignment and consistency with working userland code
|
||||||
if (result != NJ_OK) {
|
unsigned char *rgba = stbi_load_from_memory(jpg_data, (int)jpg_size, &img_w, &img_h, &channels, 4);
|
||||||
njDone();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int img_w = njGetWidth();
|
if (!rgba || img_w <= 0 || img_h <= 0) {
|
||||||
int img_h = njGetHeight();
|
|
||||||
unsigned char *rgb = njGetImage();
|
|
||||||
|
|
||||||
if (!rgb || img_w <= 0 || img_h <= 0) {
|
|
||||||
njDone();
|
|
||||||
return 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_w > MAX_WP_WIDTH) screen_w = MAX_WP_WIDTH;
|
||||||
if (screen_h > MAX_WP_HEIGHT) screen_h = MAX_WP_HEIGHT;
|
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_width = screen_w;
|
||||||
wp_height = screen_h;
|
wp_height = screen_h;
|
||||||
|
|
||||||
njDone();
|
stbi_image_free(rgba);
|
||||||
|
|
||||||
graphics_set_bg_image(wp_pixels, wp_width, wp_height);
|
graphics_set_bg_image(wp_pixels, wp_width, wp_height);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -113,8 +116,11 @@ void wallpaper_process_pending(void) {
|
||||||
if (fh) {
|
if (fh) {
|
||||||
uint32_t file_size = fh->size;
|
uint32_t file_size = fh->size;
|
||||||
if (file_size > 0 && file_size <= 4 * 1024 * 1024) {
|
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) {
|
if (buf) {
|
||||||
|
mem_memset(buf, 0, padded_size);
|
||||||
int total_read = 0;
|
int total_read = 0;
|
||||||
while (total_read < (int)file_size) {
|
while (total_read < (int)file_size) {
|
||||||
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);
|
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,21 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "wallpaper.h"
|
#include "wallpaper.h"
|
||||||
#include "fat32.h"
|
#include "fat32.h"
|
||||||
#include "nanojpeg.h"
|
#define STBI_NO_STDIO
|
||||||
|
#include "userland/stb_image.h"
|
||||||
#include "memory_manager.h"
|
#include "memory_manager.h"
|
||||||
#include "disk.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);
|
extern void serial_write(const char *str);
|
||||||
|
|
||||||
static bool str_eq(const char *s1, const char *s2) {
|
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;
|
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
|
// Helper to check if string starts with prefix
|
||||||
static bool str_starts_with(const char *str, const char *prefix) {
|
static bool str_starts_with(const char *str, const char *prefix) {
|
||||||
while(*prefix) {
|
while(*prefix) {
|
||||||
|
|
@ -595,20 +615,17 @@ static uint32_t* thumb_cache_decode(const char *path) {
|
||||||
|
|
||||||
if (total <= 0) { kfree(buf); return NULL; }
|
if (total <= 0) { kfree(buf); return NULL; }
|
||||||
|
|
||||||
// Decode JPEG
|
// Decode image
|
||||||
njInit();
|
int img_w, img_h, channels;
|
||||||
if (njDecode(buf, total) != NJ_OK) {
|
unsigned char *img = stbi_load_from_memory(buf, total, &img_w, &img_h, &channels, 4);
|
||||||
serial_write("[WM] njDecode failed for deferred thumb\n");
|
if (!img || img_w <= 0 || img_h <= 0) {
|
||||||
njDone();
|
serial_write("[WM] stbi_load_from_memory failed for deferred thumb\n");
|
||||||
|
if (img) stbi_image_free(img);
|
||||||
kfree(buf);
|
kfree(buf);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
serial_write("[WM] njDecode OK for deferred thumb\n");
|
serial_write("[WM] stbi_load_from_memory OK for deferred thumb\n");
|
||||||
|
|
||||||
int img_w = njGetWidth();
|
|
||||||
int img_h = njGetHeight();
|
|
||||||
unsigned char *img = njGetImage();
|
|
||||||
|
|
||||||
// Store in cache — downscale to 48x48
|
// Store in cache — downscale to 48x48
|
||||||
int slot = thumb_cache_next;
|
int slot = thumb_cache_next;
|
||||||
|
|
@ -626,16 +643,16 @@ static uint32_t* thumb_cache_decode(const char *path) {
|
||||||
int sy = ty * img_h / 48;
|
int sy = ty * img_h / 48;
|
||||||
if (sx >= img_w) sx = img_w - 1;
|
if (sx >= img_w) sx = img_w - 1;
|
||||||
if (sy >= img_h) sy = img_h - 1;
|
if (sy >= img_h) sy = img_h - 1;
|
||||||
int idx = (sy * img_w + sx) * 3;
|
int idx = (sy * img_w + sx) * 4;
|
||||||
uint32_t r = img[idx], g = img[idx+1], b = img[idx+2];
|
uint32_t r = img[idx], g = img[idx+1], b = img[idx+2], a = img[idx+3];
|
||||||
thumb_cache[slot].pixels[ty * 48 + tx] = 0xFF000000 | (r << 16) | (g << 8) | b;
|
thumb_cache[slot].pixels[ty * 48 + tx] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thumb_cache[slot].valid = true;
|
thumb_cache[slot].valid = true;
|
||||||
thumb_cache[slot].failed = false;
|
thumb_cache[slot].failed = false;
|
||||||
|
|
||||||
njDone();
|
stbi_image_free(img);
|
||||||
kfree(buf);
|
kfree(buf);
|
||||||
|
|
||||||
return thumb_cache[slot].pixels;
|
return thumb_cache[slot].pixels;
|
||||||
|
|
@ -1226,7 +1243,7 @@ void wm_paint(void) {
|
||||||
} else {
|
} else {
|
||||||
if (str_ends_with(icon->name, ".elf")) draw_elf_icon(icon->x, icon->y, icon->name);
|
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, ".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/";
|
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;
|
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);
|
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);
|
process_create_elf("/bin/paint.elf", path);
|
||||||
} else if (str_ends_with(icon->name, ".md")) {
|
} else if (str_ends_with(icon->name, ".md")) {
|
||||||
process_create_elf("/bin/markdown.elf", path);
|
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);
|
process_create_elf("/bin/viewer.elf", path);
|
||||||
} else {
|
} else {
|
||||||
process_create_elf("/bin/txtedit.elf", path);
|
process_create_elf("/bin/txtedit.elf", path);
|
||||||
|
|
|
||||||
0
test.gif
Normal file
0
test.gif
Normal file
Loading…
Reference in a new issue