V1.61 feature update: JPEG image viewer and JPEG wallpaper setting.

This commit is contained in:
Chris 2026-02-24 21:51:41 +01:00
parent f694c490a6
commit 106adf1ac8
40 changed files with 16895 additions and 46 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/nanojpeg.o Normal file

Binary file not shown.

BIN
build/nj_kernel.o Normal file

Binary file not shown.

BIN
build/viewer.o Normal file

Binary file not shown.

Binary file not shown.

BIN
build/wallpaper.o Normal file

Binary file not shown.

BIN
build/wallpaper_data.o Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/kernel/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -14,8 +14,8 @@ static void about_paint(Window *win) {
// Version info
draw_string(offset_x, offset_y + 105, "BoredOS 'Panda'", COLOR_WHITE);
draw_string(offset_x, offset_y + 120, "BoredOS Version 1.60", COLOR_WHITE);
draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.1", COLOR_WHITE);
draw_string(offset_x, offset_y + 120, "BoredOS Version 1.61", COLOR_WHITE);
draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.2", COLOR_WHITE);
// Copyright
draw_string(offset_x, offset_y + 150, "(C) 2026 boreddevnl.", COLOR_WHITE);

View file

@ -2,6 +2,6 @@
void cli_cmd_boredver(char *args) {
(void)args;
cli_write("BoredOS v1.60\n");
cli_write("BoredOS Kernel V2.5.1\n");
cli_write("BoredOS v1.61\n");
cli_write("BoredOS Kernel V2.5.2\n");
}

View file

@ -4,6 +4,7 @@
#include "wm.h"
#include "network.h"
#include "cli_apps/cli_utils.h"
#include "wallpaper.h"
Window win_control_panel;
@ -313,6 +314,41 @@ static void control_panel_paint_wallpaper(Window *win) {
// Apply button (rounded)
draw_rounded_rect_filled(button_x, button_y + 25, 70, 25, 6, COLOR_DARK_PANEL);
draw_string(button_x + 18, button_y + 33, "Apply", COLOR_DARK_TEXT);
// Wallpaper Images section
button_y += 60;
draw_string(offset_x, button_y, "Wallpapers:", COLOR_DARK_TEXT);
button_y += 20;
// Draw Moon thumbnail (pre-generated at init time)
uint32_t *moon_thumb = wallpaper_get_thumb(0);
draw_rounded_rect_filled(button_x, button_y, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 24, 6, COLOR_DARK_PANEL);
if (moon_thumb && wallpaper_thumb_valid(0)) {
for (int ty = 0; ty < WALLPAPER_THUMB_H; ty++) {
for (int tx = 0; tx < WALLPAPER_THUMB_W; tx++) {
put_pixel(button_x + 4 + tx, button_y + 4 + ty, moon_thumb[ty * WALLPAPER_THUMB_W + tx]);
}
}
} else {
draw_string(button_x + 20, button_y + 30, "Error", 0xFFFF4444);
}
draw_string(button_x + 30, button_y + WALLPAPER_THUMB_H + 8, "Moon", COLOR_DARK_TEXT);
// Draw Mountain thumbnail
uint32_t *mtn_thumb = wallpaper_get_thumb(1);
int thumb2_x = button_x + WALLPAPER_THUMB_W + 20;
draw_rounded_rect_filled(thumb2_x, button_y, WALLPAPER_THUMB_W + 8, WALLPAPER_THUMB_H + 24, 6, COLOR_DARK_PANEL);
if (mtn_thumb && wallpaper_thumb_valid(1)) {
for (int ty = 0; ty < WALLPAPER_THUMB_H; ty++) {
for (int tx = 0; tx < WALLPAPER_THUMB_W; tx++) {
put_pixel(thumb2_x + 4 + tx, button_y + 4 + ty, mtn_thumb[ty * WALLPAPER_THUMB_W + tx]);
}
}
} else {
draw_string(thumb2_x + 20, button_y + 30, "Error", 0xFFFF4444);
}
draw_string(thumb2_x + 16, button_y + WALLPAPER_THUMB_H + 8, "Mountain", COLOR_DARK_TEXT);
}
static void draw_input_box(int x, int y, int width, const char *text, bool focused, int cursor_pos) {
@ -675,6 +711,23 @@ static void control_panel_handle_click(Window *win, int x, int y) {
wm_refresh();
return;
}
// Wallpaper image thumbnails section
button_y += 60;
button_y += 20;
// Check Moon thumbnail click
if (x >= button_x && x < button_x + WALLPAPER_THUMB_W + 8 && y >= button_y && y < button_y + WALLPAPER_THUMB_H + 24) {
wallpaper_request_set(0);
return;
}
// Check Mountain thumbnail click
int thumb2_x = button_x + WALLPAPER_THUMB_W + 20;
if (x >= thumb2_x && x < thumb2_x + WALLPAPER_THUMB_W + 8 && y >= button_y && y < button_y + WALLPAPER_THUMB_H + 24) {
wallpaper_request_set(1);
return;
}
} else if (current_view == VIEW_NETWORK) {
int offset_x = 8;
int offset_y = 30;
@ -1041,7 +1094,7 @@ void control_panel_init(void) {
win_control_panel.x = 200;
win_control_panel.y = 150;
win_control_panel.w = 350;
win_control_panel.h = 320;
win_control_panel.h = 500;
win_control_panel.visible = false;
win_control_panel.focused = false;
win_control_panel.z_index = 0;

View file

@ -10,6 +10,7 @@
#include "notepad.h"
#include "calculator.h"
#include "minesweeper.h"
#include "viewer.h"
#include "control_panel.h"
#include "about.h"
#include "paint.h"
@ -815,6 +816,8 @@ static void explorer_open_target(const char *path) {
} else if (explorer_str_ends_with(path, ".pnt")) {
paint_load(path);
wm_bring_to_front(&win_paint);
} else if (explorer_str_ends_with(path, ".jpg") || explorer_str_ends_with(path, ".JPG")) {
viewer_open_file(path);
} else {
wm_bring_to_front(&win_editor);
editor_open_file(path);
@ -842,7 +845,7 @@ static void explorer_open_item(Window *win, int index) {
if (explorer_str_ends_with(state->items[index].name, ".shortcut")) {
Window *target = NULL;
if (explorer_strcmp(state->items[index].name, "Notepad.shortcut") == 0) {
target = &win_notepad; notepad_reset();
target = &win_notepad;
} else if (explorer_strcmp(state->items[index].name, "Calculator.shortcut") == 0) {
target = &win_calculator;
} else if (explorer_strcmp(state->items[index].name, "Terminal.shortcut") == 0) {
@ -922,6 +925,15 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c
else draw_icon(x + 5, y + 5, "");
} else if (explorer_str_ends_with(filename, ".pnt")) {
draw_paint_icon(x - 15, y + 10, "");
} else if (explorer_str_ends_with(filename, ".jpg") || explorer_str_ends_with(filename, ".JPG")) {
// Photo/image icon
draw_rect(x + 10, y + 10, 25, 20, 0xFF87CEEB); // Sky blue bg
draw_rect(x + 10, y + 22, 25, 8, 0xFF90EE90); // Green bottom (landscape)
draw_rect(x + 18, y + 16, 8, 6, 0xFFFFFF00); // Sun
draw_rect(x + 10, y + 10, 25, 1, 0xFF333333); // Border
draw_rect(x + 10, y + 10, 1, 20, 0xFF333333);
draw_rect(x + 34, y + 10, 1, 20, 0xFF333333);
draw_rect(x + 10, y + 29, 25, 1, 0xFF333333);
} else {
// Document icon - larger
draw_rect(x + 12, y + 10, 20, 25, COLOR_WHITE);
@ -973,38 +985,38 @@ static void explorer_paint(Window *win) {
drive_label[6] = ' ';
drive_label[7] = ']';
// Button at x+4, y+4, w=60 (rounded)
draw_rounded_rect_filled(win->x + 4, offset_y + 4, 60, 30, 6, COLOR_DARK_PANEL);
draw_string(win->x + 12, offset_y + 12, drive_label, COLOR_DARK_TEXT);
// Button at x+4, y+3, w=60 (rounded)
draw_rounded_rect_filled(win->x + 4, offset_y + 3, 60, 22, 5, COLOR_DARK_PANEL);
draw_string(win->x + 12, offset_y + 8, drive_label, COLOR_DARK_TEXT);
// Draw path bar (shifted right, rounded, dark mode)
int path_height = 30;
int path_height = 22;
int path_x = offset_x + 64;
int path_w = win->w - 16 - 64;
draw_rounded_rect_filled(path_x, offset_y + 4, path_w, path_height, 6, COLOR_DARK_PANEL);
draw_string(path_x + 6, offset_y + 10, "Path", COLOR_DARK_TEXT);
draw_string(path_x + 46, offset_y + 10, state->current_path, COLOR_DARK_TEXT);
draw_rounded_rect_filled(path_x, offset_y + 3, path_w, path_height, 5, COLOR_DARK_PANEL);
draw_string(path_x + 6, offset_y + 8, "Path", COLOR_DARK_TEXT);
draw_string(path_x + 46, offset_y + 8, state->current_path, COLOR_DARK_TEXT);
// Draw dropdown menu button (right-aligned, before back button, rounded)
int dropdown_btn_x = win->x + win->w - 90;
draw_rounded_rect_filled(dropdown_btn_x, offset_y + 4, 35, 30, 6, COLOR_DARK_PANEL);
draw_string(dropdown_btn_x + 10, offset_y + 10, "...", COLOR_DARK_TEXT);
draw_rounded_rect_filled(dropdown_btn_x, offset_y + 3, 35, 22, 5, COLOR_DARK_PANEL);
draw_string(dropdown_btn_x + 10, offset_y + 8, "...", COLOR_DARK_TEXT);
// Draw back button (right-aligned, rounded)
draw_rounded_rect_filled(win->x + win->w - 40, offset_y + 4, 30, 30, 6, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 32, offset_y + 10, "<", COLOR_DARK_TEXT);
draw_rounded_rect_filled(win->x + win->w - 40, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 32, offset_y + 8, "<", COLOR_DARK_TEXT);
// Draw scroll buttons (left of dropdown, rounded)
draw_rounded_rect_filled(win->x + win->w - 160, offset_y + 4, 30, 30, 6, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 150, offset_y + 10, "^", COLOR_DARK_TEXT);
draw_rounded_rect_filled(win->x + win->w - 125, offset_y + 4, 30, 30, 6, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 115, offset_y + 10, "v", COLOR_DARK_TEXT);
draw_rounded_rect_filled(win->x + win->w - 160, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 150, offset_y + 8, "^", COLOR_DARK_TEXT);
draw_rounded_rect_filled(win->x + win->w - 125, offset_y + 3, 30, 22, 5, COLOR_DARK_PANEL);
draw_string(win->x + win->w - 115, offset_y + 8, "v", COLOR_DARK_TEXT);
// Draw file list
int content_start_y = offset_y + 40;
int content_start_y = offset_y + 30;
// Clip content to window area (excluding borders and top bar)
graphics_set_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 64 - 4);
graphics_set_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 54 - 4);
for (int i = 0; i < state->item_count; i++) {
int row = i / EXPLORER_COLS;
@ -1044,7 +1056,7 @@ static void explorer_paint(Window *win) {
// Draw Drive Menu if visible (dark mode)
if (state->drive_menu_visible) {
int menu_x = win->x + 4;
int menu_y = offset_y + 34;
int menu_y = offset_y + 26;
int menu_w = 80;
int count = disk_get_count();
int menu_h = count * 25;
@ -1067,7 +1079,7 @@ static void explorer_paint(Window *win) {
draw_rounded_rect_filled(menu_x + 2, menu_y + i*25 + 2, menu_w - 4, 21, 4, 0xFF4A90E2);
draw_string(menu_x + 5, menu_y + i*25 + 6, buf, COLOR_WHITE);
} else {
draw_string(menu_x + 5, menu_y + i*25 + 6, buf, COLOR_BLACK);
draw_string(menu_x + 5, menu_y + i*25 + 6, buf, COLOR_DARK_TEXT);
}
}
}
@ -1076,7 +1088,7 @@ static void explorer_paint(Window *win) {
// Draw dropdown menu if visible
if (state->dropdown_menu_visible) {
int menu_x = dropdown_btn_x;
int menu_y = offset_y + 34;
int menu_y = offset_y + 26;
// Draw menu background
draw_rounded_rect_filled(menu_x, menu_y, DROPDOWN_MENU_WIDTH, dropdown_menu_item_height * DROPDOWN_MENU_ITEMS, 6, COLOR_DARK_PANEL);
@ -1221,7 +1233,6 @@ static void explorer_paint(Window *win) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
// Rename dialog (modern)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
draw_string(dlg_x + 10, dlg_y + 10, "Rename", COLOR_WHITE);
@ -1396,7 +1407,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
// Handle Drive Menu Selection
if (state->drive_menu_visible) {
int menu_x = 4; // Window relative
int menu_y = 58; // 24+34
int menu_y = 50; // 24+26
int menu_w = 80;
int count = disk_get_count();
int menu_h = count * 25;
@ -1425,7 +1436,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
// Handle dropdown menu clicks
if (state->dropdown_menu_visible) {
int dropdown_btn_x = win->w - 90; // Window-relative
int menu_y = 58; // Window-relative (offset_y + 34, where offset_y = 24)
int menu_y = 50; // Window-relative (offset_y + 26, where offset_y = 24)
// New File
if (x >= dropdown_btn_x && x < dropdown_btn_x + DROPDOWN_MENU_WIDTH &&
@ -1463,8 +1474,8 @@ static void explorer_handle_click(Window *win, int x, int y) {
// x, y are already relative to window (0,0 is top-left of window content area)
// Check Drive Button
int button_y = 28;
if (x >= 4 && x < 64 && y >= button_y && y < button_y + 30) {
int button_y = 27;
if (x >= 4 && x < 64 && y >= button_y && y < button_y + 22) {
state->drive_menu_visible = !state->drive_menu_visible;
state->dropdown_menu_visible = false; // Close other menu
return;
@ -1472,7 +1483,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
// Check dropdown menu button
if (x >= win->w - 90 && x < win->w - 55 &&
y >= button_y && y < button_y + 30) {
y >= button_y && y < button_y + 22) {
// Dropdown menu button clicked
dropdown_menu_toggle(win);
state->drive_menu_visible = false; // Close other menu
@ -1481,7 +1492,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
// Check back button (right-aligned)
if (x >= win->w - 40 && x < win->w - 10 &&
y >= button_y && y < button_y + 30) {
y >= button_y && y < button_y + 22) {
// Back button clicked
explorer_navigate_to(win, "..");
return;
@ -1490,14 +1501,14 @@ static void explorer_handle_click(Window *win, int x, int y) {
// Check scroll buttons
// Up: w-160
if (x >= win->w - 160 && x < win->w - 130 &&
y >= button_y && y < button_y + 30) {
y >= button_y && y < button_y + 22) {
if (state->explorer_scroll_row > 0) state->explorer_scroll_row--;
return;
}
// Down: w-125
if (x >= win->w - 125 && x < win->w - 95 &&
y >= button_y && y < button_y + 30) {
y >= button_y && y < button_y + 22) {
int total_rows = (state->item_count + EXPLORER_COLS - 1) / EXPLORER_COLS;
if (total_rows == 0) total_rows = 1;
if (state->explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) state->explorer_scroll_row++;
@ -1505,7 +1516,7 @@ static void explorer_handle_click(Window *win, int x, int y) {
}
// File items start at y=64 relative to window
int content_start_y = 64;
int content_start_y = 54;
int offset_x = 4;
for (int i = 0; i < state->item_count; i++) {
@ -1676,7 +1687,7 @@ static void explorer_handle_key(Window *win, char c) {
static void explorer_handle_right_click(Window *win, int x, int y) {
ExplorerState *state = (ExplorerState*)win->data;
// File items start at y=64 relative to window
int content_start_y = 64;
int content_start_y = 54;
int offset_x = 4;
for (int i = 0; i < state->item_count; i++) {
@ -1840,9 +1851,9 @@ bool explorer_get_file_at(int screen_x, int screen_y, char *out_path, bool *is_d
int rel_x = screen_x - win->x;
int rel_y = screen_y - win->y;
if (rel_x < 4 || rel_x > win->w - 4 || rel_y < 64 || rel_y > win->h - 4) continue;
if (rel_x < 4 || rel_x > win->w - 4 || rel_y < 54 || rel_y > win->h - 4) continue;
int content_start_y = 64;
int content_start_y = 54;
int offset_x = 4;
for (int i = 0; i < state->item_count; i++) {

View file

@ -11,6 +11,12 @@ static uint32_t g_bg_color = 0xFF696969; // Dark gray background
static uint32_t g_bg_pattern[PATTERN_SIZE * PATTERN_SIZE];
static bool g_use_pattern = false;
// Wallpaper image support
static uint32_t *g_bg_image = NULL;
static int g_bg_image_w = 0;
static int g_bg_image_h = 0;
static bool g_use_image = false;
// Dirty rectangle tracking
static DirtyRect g_dirty = {0, 0, 0, 0, false};
@ -277,7 +283,24 @@ void draw_string(int x, int y, const char *s, uint32_t color) {
void draw_desktop_background(void) {
if (!g_fb) return;
if (g_use_pattern) {
if (g_use_image && g_bg_image) {
// Draw wallpaper image (stretch/scale to screen)
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
if (g_clip_enabled) {
x1 = g_clip_x; y1 = g_clip_y;
x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h;
}
for (int y = y1; y < y2; y++) {
int src_y = y * g_bg_image_h / (int)g_fb->height;
if (src_y >= g_bg_image_h) src_y = g_bg_image_h - 1;
uint32_t *row = &g_back_buffer[y * g_fb->width + x1];
for (int x = x1; x < x2; x++) {
int src_x = x * g_bg_image_w / (int)g_fb->width;
if (src_x >= g_bg_image_w) src_x = g_bg_image_w - 1;
*row++ = g_bg_image[src_y * g_bg_image_w + src_x];
}
}
} else if (g_use_pattern) {
// Optimized tiled pattern: only draw within the clipping/dirty rect
int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height;
if (g_clip_enabled) {
@ -301,6 +324,7 @@ void draw_desktop_background(void) {
void graphics_set_bg_color(uint32_t color) {
g_bg_color = color;
g_use_pattern = false;
g_use_image = false;
}
void graphics_set_bg_pattern(const uint32_t *pattern) {
@ -311,6 +335,15 @@ void graphics_set_bg_pattern(const uint32_t *pattern) {
g_bg_pattern[i] = pattern[i];
}
g_use_pattern = true;
g_use_image = false;
}
void graphics_set_bg_image(uint32_t *pixels, int w, int h) {
g_bg_image = pixels;
g_bg_image_w = w;
g_bg_image_h = h;
g_use_image = true;
g_use_pattern = false;
}
void draw_boredos_logo(int x, int y, int scale) {

View file

@ -21,6 +21,7 @@ void draw_string(int x, int y, const char *s, uint32_t color);
void draw_desktop_background(void);
void graphics_set_bg_color(uint32_t color);
void graphics_set_bg_pattern(const uint32_t *pattern); // 128x128 pattern
void graphics_set_bg_image(uint32_t *pixels, int w, int h); // Full-screen wallpaper image
void draw_boredos_logo(int x, int y, int scale);

BIN
src/kernel/images/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/kernel/images/wallpapers/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -9,6 +9,8 @@
#include "io.h"
#include "memory_manager.h"
#include "platform.h"
#include "wallpaper.h"
#include "viewer.h"
// --- Limine Requests ---
__attribute__((used, section(".requests")))
@ -101,6 +103,8 @@ void kmain(void) {
// Timer interrupt will drive the redraw system
while (1) {
wm_process_input();
wallpaper_process_pending();
viewer_process_pending();
asm("hlt");
}
}

968
src/kernel/nanojpeg.c Normal file
View file

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

10
src/kernel/nanojpeg.h Normal file
View file

@ -0,0 +1,10 @@
// 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

28
src/kernel/nj_kernel.c Normal file
View file

@ -0,0 +1,28 @@
// nj_kernel.c - Kernel adapter for NanoJPEG memory functions
// Provides njAllocMem, njFreeMem, njFillMem, njCopyMem for NJ_USE_LIBC=0 mode
#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];
}
}

332
src/kernel/viewer.c Normal file
View file

@ -0,0 +1,332 @@
// viewer.c - Image Viewer app for BoredOS
// Opens .jpg files and displays the decoded image in a window
#include "viewer.h"
#include "nanojpeg.h"
#include "graphics.h"
#include "fat32.h"
#include "memory_manager.h"
#include "wallpaper.h"
#include "io.h"
#include <stddef.h>
Window win_viewer;
// Viewer state
#define VIEWER_MAX_W 800
#define VIEWER_MAX_H 600
static uint32_t viewer_pixels[VIEWER_MAX_W * VIEWER_MAX_H];
static int viewer_img_w = 0;
static int viewer_img_h = 0;
static char viewer_title[64] = "Viewer";
static bool viewer_has_image = false;
// Deferred open: click handler stores path, main loop decodes
static char viewer_pending_path[256];
static volatile bool viewer_open_pending = false;
// Store the file path for "Set as Wallpaper"
static char viewer_file_path[256];
// String helpers
static int viewer_strlen(const char *s) {
int len = 0;
while (s[len]) len++;
return len;
}
static void viewer_strcpy(char *dst, const char *src) {
while (*src) *dst++ = *src++;
*dst = 0;
}
// Simple nearest-neighbor scale from decoded RGB to ARGB pixel buffer
static void viewer_scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
uint32_t *dst, int dst_w, int dst_h) {
for (int y = 0; y < dst_h; y++) {
int src_y = y * src_h / dst_h;
if (src_y >= src_h) src_y = src_h - 1;
for (int x = 0; x < dst_w; x++) {
int src_x = x * src_w / dst_w;
if (src_x >= src_w) src_x = src_w - 1;
int idx = (src_y * src_w + src_x) * 3;
unsigned char r = rgb[idx];
unsigned char g = rgb[idx + 1];
unsigned char b = rgb[idx + 2];
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
}
}
static void viewer_paint(Window *win) {
int cx = win->x + 4;
int cy = win->y + 24;
int cw = win->w - 8;
int ch = win->h - 28;
draw_rect(cx, cy, cw, ch, 0xFF1A1A1A);
if (!viewer_has_image) {
draw_string(cx + 20, cy + ch / 2, "No image loaded", 0xFF888888);
return;
}
// Calculate display size (fit within window, keep aspect ratio)
int disp_w = viewer_img_w;
int disp_h = viewer_img_h;
if (disp_w > cw - 8) {
disp_h = disp_h * (cw - 8) / disp_w;
disp_w = cw - 8;
}
if (disp_h > ch - 40) {
disp_w = disp_w * (ch - 40) / disp_h;
disp_h = ch - 40;
}
// Center in window
int ox = cx + (cw - disp_w) / 2;
int oy = cy + (ch - disp_h - 30) / 2;
// Draw the image pixel by pixel (nearest-neighbor from stored buffer)
for (int y = 0; y < disp_h; y++) {
int src_y = y * viewer_img_h / disp_h;
if (src_y >= viewer_img_h) src_y = viewer_img_h - 1;
for (int x = 0; x < disp_w; x++) {
int src_x = x * viewer_img_w / disp_w;
if (src_x >= viewer_img_w) src_x = viewer_img_w - 1;
uint32_t pixel = viewer_pixels[src_y * viewer_img_w + src_x];
put_pixel(ox + x, oy + y, pixel);
}
}
// Draw "Set as Wallpaper" button at the bottom
int btn_w = 160;
int btn_h = 22;
int btn_x = cx + (cw - btn_w) / 2;
int btn_y = win->y + win->h - 30;
draw_rounded_rect_filled(btn_x, btn_y, btn_w, btn_h, 6, 0xFF2D2D2D);
draw_string(btn_x + 10, btn_y + 6, "Set as Wallpaper", 0xFFF0F0F0);
}
static void viewer_handle_click(Window *win, int x, int y) {
if (!viewer_has_image) return;
int cx = 4;
int cw = win->w - 8;
// Check "Set as Wallpaper" button
int btn_w = 160;
int btn_x = cx + (cw - btn_w) / 2;
int btn_y = win->h - 30;
if (x >= btn_x && x < btn_x + btn_w && y >= btn_y && y < btn_y + 22) {
// Queue wallpaper change from file (deferred to main loop)
wallpaper_request_set_from_file(viewer_file_path);
}
}
static void viewer_handle_key(Window *win, char c) {
(void)win;
(void)c;
}
// Simple serial output for debugging
static void v_serial_char(char c) {
while (!(inb(0x3F8 + 5) & 0x20));
outb(0x3F8, c);
}
static void v_serial_str(const char *s) { while (*s) v_serial_char(*s++); }
static void v_serial_num(int n) {
if (n < 0) { v_serial_char('-'); n = -n; }
if (n >= 10) v_serial_num(n / 10);
v_serial_char('0' + (n % 10));
}
// Called from interrupt context - just queue the path for later processing
void viewer_open_file(const char *path) {
v_serial_str("[VIEWER] open_file queued: ");
v_serial_str(path);
v_serial_str("\n");
viewer_strcpy(viewer_pending_path, path);
viewer_open_pending = true;
}
// Process deferred viewer open (called from main loop, NOT interrupt context)
void viewer_process_pending(void) {
if (!viewer_open_pending) return;
viewer_open_pending = false;
const char *path = viewer_pending_path;
v_serial_str("[VIEWER] process_pending: ");
v_serial_str(path);
v_serial_str("\n");
FAT32_FileHandle *fh = fat32_open(path, "r");
if (!fh) {
v_serial_str("[VIEWER] fat32_open FAILED\n");
return;
}
uint32_t file_size = fh->size;
v_serial_str("[VIEWER] file_size=");
v_serial_num((int)file_size);
v_serial_str("\n");
if (file_size == 0 || file_size > 2 * 1024 * 1024) {
v_serial_str("[VIEWER] file too big or empty\n");
fat32_close(fh);
return;
}
unsigned char *buf = (unsigned char*)kmalloc(file_size);
if (!buf) {
v_serial_str("[VIEWER] kmalloc FAILED\n");
fat32_close(fh);
return;
}
int total_read = 0;
while (total_read < (int)file_size) {
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);
if (chunk <= 0) break;
total_read += chunk;
}
fat32_close(fh);
v_serial_str("[VIEWER] read ");
v_serial_num(total_read);
v_serial_str(" bytes\n");
if (total_read <= 0) {
kfree(buf);
return;
}
// Decode JPEG (now running in main loop, safe context)
njInit();
nj_result_t result = njDecode(buf, total_read);
v_serial_str("[VIEWER] njDecode returned: ");
v_serial_num((int)result);
v_serial_str("\n");
if (result != NJ_OK) {
njDone();
kfree(buf);
return;
}
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *rgb = njGetImage();
v_serial_str("[VIEWER] decoded ");
v_serial_num(img_w);
v_serial_str("x");
v_serial_num(img_h);
v_serial_str("\n");
if (!rgb || img_w <= 0 || img_h <= 0) {
njDone();
kfree(buf);
return;
}
// Scale to fit viewer buffer
int fit_w = img_w;
int fit_h = img_h;
if (fit_w > VIEWER_MAX_W) {
fit_h = fit_h * VIEWER_MAX_W / fit_w;
fit_w = VIEWER_MAX_W;
}
if (fit_h > VIEWER_MAX_H) {
fit_w = fit_w * VIEWER_MAX_H / fit_h;
fit_h = VIEWER_MAX_H;
}
viewer_scale_rgb_to_argb(rgb, 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();
kfree(buf);
// Store the file path for "Set as Wallpaper"
viewer_strcpy(viewer_file_path, path);
// Update title - extract filename from path
const char *fname = path;
int plen = viewer_strlen(path);
for (int i = plen - 1; i >= 0; i--) {
if (path[i] == '/') {
fname = &path[i + 1];
break;
}
}
viewer_title[0] = 'V'; viewer_title[1] = 'i'; viewer_title[2] = 'e';
viewer_title[3] = 'w'; viewer_title[4] = 'e'; viewer_title[5] = 'r';
viewer_title[6] = ' '; viewer_title[7] = '-'; viewer_title[8] = ' ';
int ti = 9;
for (int i = 0; fname[i] && ti < 60; i++) {
viewer_title[ti++] = fname[i];
}
viewer_title[ti] = 0;
// Resize window to fit image
win_viewer.w = fit_w + 16;
if (win_viewer.w < 200) win_viewer.w = 200;
win_viewer.h = fit_h + 64;
if (win_viewer.h < 100) win_viewer.h = 100;
// Reset position to ensure visibility
win_viewer.x = 100;
win_viewer.y = 50;
v_serial_str("[VIEWER] window: x=");
v_serial_num(win_viewer.x);
v_serial_str(" y=");
v_serial_num(win_viewer.y);
v_serial_str(" w=");
v_serial_num(win_viewer.w);
v_serial_str(" h=");
v_serial_num(win_viewer.h);
v_serial_str(" fit=");
v_serial_num(fit_w);
v_serial_str("x");
v_serial_num(fit_h);
v_serial_str("\n");
// Show and bring to front
win_viewer.visible = true;
wm_bring_to_front(&win_viewer);
v_serial_str("[VIEWER] z_index=");
v_serial_num(win_viewer.z_index);
v_serial_str(" visible=");
v_serial_num(win_viewer.visible);
v_serial_str(" focused=");
v_serial_num(win_viewer.focused);
v_serial_str("\n");
v_serial_str("[VIEWER] window shown!\n");
}
void viewer_init(void) {
win_viewer.title = viewer_title;
viewer_title[0] = 'V'; viewer_title[1] = 'i'; viewer_title[2] = 'e';
viewer_title[3] = 'w'; viewer_title[4] = 'e'; viewer_title[5] = 'r';
viewer_title[6] = 0;
win_viewer.x = 100;
win_viewer.y = 50;
win_viewer.w = 500;
win_viewer.h = 400;
win_viewer.visible = false;
win_viewer.paint = viewer_paint;
win_viewer.handle_click = viewer_handle_click;
win_viewer.handle_key = viewer_handle_key;
win_viewer.handle_right_click = NULL;
win_viewer.data = NULL;
// Window is registered directly in wm_init's all_windows array
v_serial_str("[VIEWER] init done, win_viewer paint=");
v_serial_num(win_viewer.paint != NULL);
v_serial_str("\n");
}

13
src/kernel/viewer.h Normal file
View file

@ -0,0 +1,13 @@
// viewer.h - Image Viewer app for BoredOS
#ifndef VIEWER_H
#define VIEWER_H
#include "wm.h"
extern Window win_viewer;
void viewer_init(void);
void viewer_open_file(const char *path); // Safe from interrupt context (deferred)
void viewer_process_pending(void); // Call from main loop only
#endif // VIEWER_H

292
src/kernel/wallpaper.c Normal file
View file

@ -0,0 +1,292 @@
// wallpaper.c - Wallpaper management for BoredOS
#include "wallpaper.h"
#include "nanojpeg.h"
#include "graphics.h"
#include "fat32.h"
#include "memory_manager.h"
#include "wallpaper_data.h"
#include "wm.h"
#include "io.h"
#include <stddef.h>
// Static buffer for the current wallpaper (max 1920x1080)
#define MAX_WP_WIDTH 1920
#define MAX_WP_HEIGHT 1080
static uint32_t wp_pixels[MAX_WP_WIDTH * MAX_WP_HEIGHT];
static int wp_width = 0;
static int wp_height = 0;
// Pre-generated thumbnails
static uint32_t thumb_moon[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H];
static uint32_t thumb_mountain[WALLPAPER_THUMB_W * WALLPAPER_THUMB_H];
static bool thumbs_valid[WALLPAPER_COUNT] = {false, false};
// Deferred wallpaper action (set from interrupt context, processed in main loop)
static volatile int pending_wallpaper_index = -1;
static volatile const char *pending_wallpaper_path = NULL;
static char pending_path_buf[256];
const char *wallpaper_names[WALLPAPER_COUNT] = {
"Moon",
"Mountain"
};
// Simple nearest-neighbor scale from decoded RGB to ARGB pixel buffer
static void scale_rgb_to_argb(const unsigned char *rgb, int src_w, int src_h,
uint32_t *dst, int dst_w, int dst_h) {
for (int y = 0; y < dst_h; y++) {
int src_y = y * src_h / dst_h;
if (src_y >= src_h) src_y = src_h - 1;
for (int x = 0; x < dst_w; x++) {
int src_x = x * src_w / dst_w;
if (src_x >= src_w) src_x = src_w - 1;
int idx = (src_y * src_w + src_x) * 3;
unsigned char r = rgb[idx];
unsigned char g = rgb[idx + 1];
unsigned char b = rgb[idx + 2];
dst[y * dst_w + x] = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
}
}
// Decode JPEG data from memory and set as wallpaper (MUST be called from non-interrupt context)
static int decode_and_set_wallpaper(const unsigned char *jpg_data, unsigned int jpg_size) {
njInit();
nj_result_t result = njDecode(jpg_data, (int)jpg_size);
if (result != NJ_OK) {
njDone();
return 0;
}
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *rgb = njGetImage();
if (!rgb || img_w <= 0 || img_h <= 0) {
njDone();
return 0;
}
// Scale to screen size
int screen_w = get_screen_width();
int screen_h = get_screen_height();
if (screen_w > MAX_WP_WIDTH) screen_w = MAX_WP_WIDTH;
if (screen_h > MAX_WP_HEIGHT) screen_h = MAX_WP_HEIGHT;
scale_rgb_to_argb(rgb, img_w, img_h, wp_pixels, screen_w, screen_h);
wp_width = screen_w;
wp_height = screen_h;
njDone();
graphics_set_bg_image(wp_pixels, wp_width, wp_height);
return 1;
}
// Simple serial output for debugging (COM1 = 0x3F8)
static void serial_char(char c) {
while (!(inb(0x3F8 + 5) & 0x20)); // Wait for transmit ready
outb(0x3F8, c);
}
static void serial_str(const char *s) {
while (*s) serial_char(*s++);
}
static void serial_num(int n) {
if (n < 0) { serial_char('-'); n = -n; }
if (n >= 10) serial_num(n / 10);
serial_char('0' + (n % 10));
}
static void serial_hex(unsigned int n) {
const char hex[] = "0123456789ABCDEF";
serial_str("0x");
for (int i = 28; i >= 0; i -= 4) {
serial_char(hex[(n >> i) & 0xF]);
}
}
// Decode JPEG and generate a thumbnail (MUST be called from non-interrupt context)
static int decode_thumbnail(const unsigned char *jpg_data, unsigned int jpg_size,
uint32_t *out_pixels, int thumb_w, int thumb_h) {
serial_str("[WP] decode_thumbnail: data=");
serial_hex((unsigned int)(unsigned long)jpg_data);
serial_str(" size=");
serial_num((int)jpg_size);
serial_str(" first bytes: ");
for (int i = 0; i < 4 && i < (int)jpg_size; i++) {
serial_hex(jpg_data[i]);
serial_char(' ');
}
serial_str("\n");
njInit();
serial_str("[WP] njInit done, calling njDecode...\n");
nj_result_t result = njDecode(jpg_data, (int)jpg_size);
serial_str("[WP] njDecode returned: ");
serial_num((int)result);
serial_str("\n");
if (result != NJ_OK) {
njDone();
// Fill with error indicator color based on error code
uint32_t err_color;
switch (result) {
case NJ_NO_JPEG: err_color = 0xFF880000; break; // dark red
case NJ_UNSUPPORTED: err_color = 0xFF888800; break; // dark yellow
case NJ_OUT_OF_MEM: err_color = 0xFF008800; break; // dark green
case NJ_INTERNAL_ERR: err_color = 0xFF000088; break; // dark blue
case NJ_SYNTAX_ERROR: err_color = 0xFF880088; break; // dark magenta
default: err_color = 0xFF444444; break; // grey
}
for (int i = 0; i < thumb_w * thumb_h; i++) {
out_pixels[i] = err_color;
}
return 1; // Return 1 (valid) so the colored diagnostic is visible!
}
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *rgb = njGetImage();
serial_str("[WP] decoded: ");
serial_num(img_w);
serial_str("x");
serial_num(img_h);
serial_str("\n");
if (!rgb || img_w <= 0 || img_h <= 0) {
njDone();
for (int i = 0; i < thumb_w * thumb_h; i++) {
out_pixels[i] = 0xFF444444;
}
return 1; // visible
}
scale_rgb_to_argb(rgb, img_w, img_h, out_pixels, thumb_w, thumb_h);
njDone();
return 1;
}
// Request wallpaper change by index (safe to call from interrupt context)
void wallpaper_request_set(int index) {
pending_wallpaper_index = index;
}
// Request wallpaper change by file path (safe to call from interrupt context)
void wallpaper_request_set_from_file(const char *path) {
// Copy path to buffer
int i = 0;
while (path[i] && i < 255) {
pending_path_buf[i] = path[i];
i++;
}
pending_path_buf[i] = 0;
pending_wallpaper_path = pending_path_buf;
}
// Process deferred wallpaper actions (called from main loop, NOT interrupt context)
void wallpaper_process_pending(void) {
if (pending_wallpaper_index >= 0) {
int idx = pending_wallpaper_index;
pending_wallpaper_index = -1;
const unsigned char *data = NULL;
unsigned int size = 0;
if (idx == 0) {
data = wallpaper_moon_jpg;
size = wallpaper_moon_jpg_len;
} else if (idx == 1) {
data = wallpaper_mountain_jpg;
size = wallpaper_mountain_jpg_len;
}
if (data) {
decode_and_set_wallpaper(data, size);
wm_refresh();
}
}
if (pending_wallpaper_path) {
const char *path = (const char *)pending_wallpaper_path;
pending_wallpaper_path = NULL;
// Read file from filesystem
FAT32_FileHandle *fh = fat32_open(path, "r");
if (fh) {
uint32_t file_size = fh->size;
if (file_size > 0 && file_size <= 2 * 1024 * 1024) {
unsigned char *buf = (unsigned char*)kmalloc(file_size);
if (buf) {
int total_read = 0;
while (total_read < (int)file_size) {
int chunk = fat32_read(fh, buf + total_read, (int)file_size - total_read);
if (chunk <= 0) break;
total_read += chunk;
}
fat32_close(fh);
if (total_read > 0) {
decode_and_set_wallpaper(buf, (unsigned int)total_read);
wm_refresh();
}
kfree(buf);
} else {
fat32_close(fh);
}
} else {
fat32_close(fh);
}
}
}
}
// Get pre-generated thumbnail data
uint32_t* wallpaper_get_thumb(int index) {
if (index == 0) return thumb_moon;
if (index == 1) return thumb_mountain;
return NULL;
}
bool wallpaper_thumb_valid(int index) {
if (index < 0 || index >= WALLPAPER_COUNT) return false;
return thumbs_valid[index];
}
uint32_t* wallpaper_get_pixels(void) { return wp_pixels; }
int wallpaper_get_width(void) { return wp_width; }
int wallpaper_get_height(void) { return wp_height; }
void wallpaper_init(void) {
// Create /Wallpapers directory
fat32_mkdir("/Wallpapers");
// Write moon.jpg to /Wallpapers/moon.jpg
if (!fat32_exists("/Wallpapers/moon.jpg")) {
FAT32_FileHandle *fh = fat32_open("/Wallpapers/moon.jpg", "w");
if (fh) {
fat32_write(fh, wallpaper_moon_jpg, wallpaper_moon_jpg_len);
fat32_close(fh);
}
}
// Write mountain.jpg to /Wallpapers/mountain.jpg
if (!fat32_exists("/Wallpapers/mountain.jpg")) {
FAT32_FileHandle *fh = fat32_open("/Wallpapers/mountain.jpg", "w");
if (fh) {
fat32_write(fh, wallpaper_mountain_jpg, wallpaper_mountain_jpg_len);
fat32_close(fh);
}
}
// Pre-generate thumbnails at boot time (non-interrupt context!)
thumbs_valid[0] = decode_thumbnail(wallpaper_moon_jpg, wallpaper_moon_jpg_len,
thumb_moon, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H);
thumbs_valid[1] = decode_thumbnail(wallpaper_mountain_jpg, wallpaper_mountain_jpg_len,
thumb_mountain, WALLPAPER_THUMB_W, WALLPAPER_THUMB_H);
// Set mountain.jpg as the default wallpaper
decode_and_set_wallpaper(wallpaper_mountain_jpg, wallpaper_mountain_jpg_len);
}

35
src/kernel/wallpaper.h Normal file
View file

@ -0,0 +1,35 @@
// wallpaper.h - Wallpaper management for BoredOS
#ifndef WALLPAPER_H
#define WALLPAPER_H
#include <stdint.h>
#include <stdbool.h>
// Initialize wallpaper subsystem (creates /Wallpapers dir, writes JPEGs, pre-generates thumbnails)
void wallpaper_init(void);
// Request wallpaper change by embedded index (safe from interrupt context)
void wallpaper_request_set(int index);
// Request wallpaper change by file path (safe from interrupt context)
void wallpaper_request_set_from_file(const char *path);
// Process pending wallpaper actions (call from main loop only!)
void wallpaper_process_pending(void);
// Get pre-generated thumbnail pixel buffer (index 0=moon, 1=mountain)
#define WALLPAPER_THUMB_W 100
#define WALLPAPER_THUMB_H 60
uint32_t* wallpaper_get_thumb(int index);
bool wallpaper_thumb_valid(int index);
// Wallpaper info
#define WALLPAPER_COUNT 2
extern const char *wallpaper_names[WALLPAPER_COUNT];
// Get decoded wallpaper pixel buffer
uint32_t* wallpaper_get_pixels(void);
int wallpaper_get_width(void);
int wallpaper_get_height(void);
#endif // WALLPAPER_H

15047
src/kernel/wallpaper_data.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
// wallpaper_data.h - Embedded JPEG wallpaper data (declarations only)
#ifndef WALLPAPER_DATA_H
#define WALLPAPER_DATA_H
#include <stdint.h>
// moon.jpg
extern const unsigned char wallpaper_moon_jpg[];
extern const unsigned int wallpaper_moon_jpg_len;
// mountain.jpg
extern const unsigned char wallpaper_mountain_jpg[];
extern const unsigned int wallpaper_mountain_jpg_len;
#endif // WALLPAPER_DATA_H

View file

@ -10,6 +10,8 @@
#include <stdbool.h>
#include <stddef.h>
#include "notepad.h"
#include "viewer.h"
#include "wallpaper.h"
#include "control_panel.h"
#include "about.h"
#include "minesweeper.h"
@ -933,6 +935,10 @@ void wm_paint(void) {
draw_document_icon(icon->x, icon->y, icon->name);
draw_string(icon->x + 31, icon->y + 2, "C", COLOR_APPLE_BLUE);
}
else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
draw_document_icon(icon->x, icon->y, icon->name);
draw_string(icon->x + 27, icon->y + 2, "JPG", 0xFF44BB44);
}
else draw_document_icon(icon->x, icon->y, icon->name);
}
}
@ -1330,8 +1336,6 @@ void wm_handle_click(int x, int y) {
// Reset window state on close
if (topmost == &win_explorer) {
explorer_reset();
} else if (topmost == &win_notepad) {
notepad_reset();
} else if (topmost == &win_control_panel) {
control_panel_reset();
} else if (topmost == &win_paint) {
@ -1572,7 +1576,6 @@ void wm_handle_right_click(int x, int y) {
if (str_starts_with(start_menu_pending_app, "Files")) {
explorer_open_directory("/");
} else if (str_starts_with(start_menu_pending_app, "Notepad")) {
notepad_reset();
wm_bring_to_front(&win_notepad);
} else if (str_starts_with(start_menu_pending_app, "Editor")) {
wm_bring_to_front(&win_editor);
@ -1607,7 +1610,7 @@ void wm_handle_right_click(int x, int y) {
if (icon->type == 2) { // App Shortcut
// Check name to launch app
if (str_ends_with(icon->name, "Notepad.shortcut")) {
notepad_reset(); wm_bring_to_front(&win_notepad); handled = true;
wm_bring_to_front(&win_notepad); handled = true;
} else if (str_ends_with(icon->name, "Calculator.shortcut")) {
wm_bring_to_front(&win_calculator); handled = true;
} else if (str_ends_with(icon->name, "Minesweeper.shortcut")) {
@ -1666,6 +1669,8 @@ void wm_handle_right_click(int x, int y) {
} else if (str_ends_with(icon->name, ".md")) {
markdown_open_file(path);
wm_bring_to_front(&win_markdown);
} else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
viewer_open_file(path);
} else {
editor_open_file(path);
wm_bring_to_front(&win_editor);
@ -2009,6 +2014,8 @@ void wm_init(void) {
about_init();
minesweeper_init();
paint_init();
viewer_init();
wallpaper_init();
refresh_desktop_icons();
@ -2024,7 +2031,6 @@ void wm_init(void) {
win_minesweeper.z_index = 8;
win_paint.z_index = 9;
// Register windows in array
all_windows[0] = &win_notepad;
all_windows[1] = &win_cmd;
all_windows[2] = &win_calculator;
@ -2035,7 +2041,8 @@ void wm_init(void) {
all_windows[7] = &win_about;
all_windows[8] = &win_minesweeper;
all_windows[9] = &win_paint;
window_count = 10;
all_windows[10] = &win_viewer;
window_count = 11;
// Only show Explorer and Notepad on desktop (Explorer on top)
win_explorer.visible = false;