mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
V1.61 feature update: JPEG image viewer and JPEG wallpaper setting.
This commit is contained in:
parent
f694c490a6
commit
106adf1ac8
40 changed files with 16895 additions and 46 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
BIN
boredos.iso
BIN
boredos.iso
Binary file not shown.
BIN
build/about.o
BIN
build/about.o
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/explorer.o
BIN
build/explorer.o
Binary file not shown.
BIN
build/graphics.o
BIN
build/graphics.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/nanojpeg.o
Normal file
BIN
build/nanojpeg.o
Normal file
Binary file not shown.
BIN
build/nj_kernel.o
Normal file
BIN
build/nj_kernel.o
Normal file
Binary file not shown.
BIN
build/viewer.o
Normal file
BIN
build/viewer.o
Normal file
Binary file not shown.
BIN
build/vm.o
BIN
build/vm.o
Binary file not shown.
BIN
build/wallpaper.o
Normal file
BIN
build/wallpaper.o
Normal file
Binary file not shown.
BIN
build/wallpaper_data.o
Normal file
BIN
build/wallpaper_data.o
Normal file
Binary file not shown.
BIN
build/wm.o
BIN
build/wm.o
Binary file not shown.
Binary file not shown.
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/kernel/.DS_Store
vendored
Normal file
BIN
src/kernel/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
BIN
src/kernel/images/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/kernel/images/wallpapers/.DS_Store
vendored
Normal file
BIN
src/kernel/images/wallpapers/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/kernel/images/wallpapers/moon.jpg
Normal file
BIN
src/kernel/images/wallpapers/moon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
src/kernel/images/wallpapers/mountain.jpg
Normal file
BIN
src/kernel/images/wallpapers/mountain.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
|
|
@ -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
968
src/kernel/nanojpeg.c
Normal 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; // 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
|
||||
10
src/kernel/nanojpeg.h
Normal file
10
src/kernel/nanojpeg.h
Normal 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
28
src/kernel/nj_kernel.c
Normal 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
332
src/kernel/viewer.c
Normal 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
13
src/kernel/viewer.h
Normal 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
292
src/kernel/wallpaper.c
Normal 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
35
src/kernel/wallpaper.h
Normal 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
15047
src/kernel/wallpaper_data.c
Normal file
File diff suppressed because it is too large
Load diff
15
src/kernel/wallpaper_data.h
Normal file
15
src/kernel/wallpaper_data.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue