From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: rbutil: Merge rbutil with utils folder. rbutil uses several components from the utils folder, and can be considered part of utils too. Having it in a separate folder is an arbitrary split that doesn't help anymore these days, so merge them. This also allows other utils to easily use libtools.make without the need to navigate to a different folder. Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21 --- utils/ibassoboot/jni/Android.mk | 14 + utils/ibassoboot/jni/chooser.bmp | Bin 0 -> 230454 bytes utils/ibassoboot/jni/ibassodualboot.c | 771 ++++++++++++++++++++++++++++++++ utils/ibassoboot/jni/qdbmp.c | 798 ++++++++++++++++++++++++++++++++++ utils/ibassoboot/jni/qdbmp.h | 133 ++++++ utils/ibassoboot/jni/rbmissing.bmp | Bin 0 -> 230454 bytes utils/ibassoboot/jni/usb.bmp | Bin 0 -> 230454 bytes 7 files changed, 1716 insertions(+) create mode 100644 utils/ibassoboot/jni/Android.mk create mode 100644 utils/ibassoboot/jni/chooser.bmp create mode 100644 utils/ibassoboot/jni/ibassodualboot.c create mode 100644 utils/ibassoboot/jni/qdbmp.c create mode 100644 utils/ibassoboot/jni/qdbmp.h create mode 100644 utils/ibassoboot/jni/rbmissing.bmp create mode 100644 utils/ibassoboot/jni/usb.bmp (limited to 'utils/ibassoboot') diff --git a/utils/ibassoboot/jni/Android.mk b/utils/ibassoboot/jni/Android.mk new file mode 100644 index 0000000000..9cd667c5b9 --- /dev/null +++ b/utils/ibassoboot/jni/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := MangoPlayer +LOCAL_SRC_FILES := ibassodualboot.c qdbmp.c + +TARGET_ARCH=arm +TARGET_PLATFORM=android-14 +TARGET_ARCH_ABI=armeabi + +#LOCAL_CFLAGS := -DDEBUG +#LOCAL_LDLIBS := -llog + +include $(BUILD_EXECUTABLE) diff --git a/utils/ibassoboot/jni/chooser.bmp b/utils/ibassoboot/jni/chooser.bmp new file mode 100644 index 0000000000..3e6742d600 Binary files /dev/null and b/utils/ibassoboot/jni/chooser.bmp differ diff --git a/utils/ibassoboot/jni/ibassodualboot.c b/utils/ibassoboot/jni/ibassodualboot.c new file mode 100644 index 0000000000..0458ff1b71 --- /dev/null +++ b/utils/ibassoboot/jni/ibassodualboot.c @@ -0,0 +1,771 @@ +/*************************************************************************** + * __________ __ ___ + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2014 by Ilia Sergachev: Initial Rockbox port to iBasso DX50 + * Copyright (C) 2014 by Mario Basister: iBasso DX90 port + * Copyright (C) 2014 by Simon Rothen: Initial Rockbox repository submission, additional features + * Copyright (C) 2014 by Udo Schläpfer: Code clean up, additional features + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdbmp.h" + + +/*- Android logcat ------------------------------------------------------------------------------*/ + + +#ifdef DEBUG +#include + + +static const char log_tag[] = "Rockbox Boot"; + + +void debugf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + __android_log_vprint(ANDROID_LOG_DEBUG, log_tag, fmt, ap); + va_end(ap); +} + + +void ldebugf(const char* file, int line, const char *fmt, ...) +{ + va_list ap; + /* 13: 5 literal chars and 8 chars for the line number. */ + char buf[strlen(file) + strlen(fmt) + 13]; + snprintf(buf, sizeof(buf), "%s (%d): %s", file, line, fmt); + va_start(ap, fmt); + __android_log_vprint(ANDROID_LOG_DEBUG, log_tag, buf, ap); + va_end(ap); +} + + +void debug_trace(const char* function) +{ + static const char trace_tag[] = "TRACE: "; + char msg[strlen(trace_tag) + strlen(function) + 1]; + snprintf(msg, sizeof(msg), "%s%s", trace_tag, function); + __android_log_write(ANDROID_LOG_DEBUG, log_tag, msg); +} + + +#define DEBUGF debugf +#define TRACE debug_trace(__func__) +#else +#define DEBUGF(...) +#define TRACE +#endif /* DEBUG */ + + +/*- Vold monitor --------------------------------------------------------------------------------*/ + + +/* + Without this socket iBasso Vold will not start. + iBasso Vold uses this to send status messages about storage devices. +*/ +static const char VOLD_MONITOR_SOCKET_NAME[] = "UNIX_domain"; +static int _vold_monitor_socket_fd = -1; + + +static void vold_monitor_open_socket(void) +{ + TRACE; + + unlink(VOLD_MONITOR_SOCKET_NAME); + + _vold_monitor_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if(_vold_monitor_socket_fd < 0) + { + _vold_monitor_socket_fd = -1; + return; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, VOLD_MONITOR_SOCKET_NAME, sizeof(addr.sun_path) - 1); + + if(bind(_vold_monitor_socket_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) + { + close(_vold_monitor_socket_fd); + unlink(VOLD_MONITOR_SOCKET_NAME); + _vold_monitor_socket_fd = -1; + return; + } + + if(listen(_vold_monitor_socket_fd, 1) < 0) + { + close(_vold_monitor_socket_fd); + unlink(VOLD_MONITOR_SOCKET_NAME); + _vold_monitor_socket_fd = -1; + return; + } +} + + +/* + bionic does not have pthread_cancel. + 0: Vold monitor thread stopped/ending. + 1: Vold monitor thread started/running. +*/ +static volatile sig_atomic_t _vold_monitor_active = 0; + + +/* true: sdcard not mounted. */ +static bool _sdcard_not_mounted = true; + + +/* Mutex for sdcard mounted flag. */ +static pthread_mutex_t _sdcard_mount_mtx = PTHREAD_MUTEX_INITIALIZER; + + +/* Signal condition for sdcard mounted flag. */ +static pthread_cond_t _sdcard_mount_cond = PTHREAD_COND_INITIALIZER; + + +static void* vold_monitor_run(void* nothing) +{ + _vold_monitor_active = 1; + + (void) nothing; + + DEBUGF("DEBUG %s: Thread start.", __func__); + + vold_monitor_open_socket(); + if(_vold_monitor_socket_fd < 0) + { + DEBUGF("ERROR %s: Thread end: No socket.", __func__); + + _vold_monitor_active = 0; + return 0; + } + + struct pollfd fds[1]; + fds[0].fd = _vold_monitor_socket_fd; + fds[0].events = POLLIN; + + while(_vold_monitor_active == 1) + { + poll(fds, 1, 10); + if(! (fds[0].revents & POLLIN)) + { + continue; + } + + int socket_fd = accept(_vold_monitor_socket_fd, NULL, NULL); + + if(socket_fd < 0) + { + DEBUGF("ERROR %s: accept failed.", __func__); + + continue; + } + + while(true) + { + char msg[1024]; + memset(msg, 0, sizeof(msg)); + int length = read(socket_fd, msg, sizeof(msg)); + + if(length <= 0) + { + close(socket_fd); + break; + } + + DEBUGF("DEBUG %s: msg: %s", __func__, msg); + + if(strcmp(msg, "Volume flash /mnt/sdcard state changed from 3 (Checking) to 4 (Mounted)") == 0) + { + pthread_mutex_lock(&_sdcard_mount_mtx); + _sdcard_not_mounted = false; + pthread_cond_signal(&_sdcard_mount_cond); + pthread_mutex_unlock(&_sdcard_mount_mtx); + } + } + } + + close(_vold_monitor_socket_fd); + unlink(VOLD_MONITOR_SOCKET_NAME); + _vold_monitor_socket_fd = -1; + + DEBUGF("DEBUG %s: Thread end.", __func__); + + _vold_monitor_active = 0; + return 0; +} + + +/* Vold monitor thread. */ +static pthread_t _vold_monitor_thread; + + +static void vold_monitor_start(void) +{ + TRACE; + + if(_vold_monitor_active == 0) + { + pthread_create(&_vold_monitor_thread, NULL, vold_monitor_run, NULL); + } +} + + +static void vold_monitor_stop(void) +{ + TRACE; + + if(_vold_monitor_active == 1) + { + _vold_monitor_active = 0; + int ret = pthread_join(_vold_monitor_thread, NULL); + DEBUGF("DEBUG %s: Thread joined: ret: %d.", __func__, ret); + } +} + + +/*- Input handler -------------------------------------------------------------------------------*/ + + +/* Input devices monitored with poll API. */ +static struct pollfd* _fds = NULL; + + +/* Number of input devices monitored with poll API. */ +static nfds_t _nfds = 0; + + +/* The names of the devices in _fds. */ +static char** _device_names = NULL; + + +/* Open device device_name and add it to the list of polled devices. */ +static void open_device(const char* device_name) +{ + int fd = open(device_name, O_RDONLY); + if(fd == -1) + { + DEBUGF("ERROR %s: open failed on %s.", __func__, device_name); + exit(-1); + } + + struct pollfd* new_fds = realloc(_fds, sizeof(struct pollfd) * (_nfds + 1)); + if(new_fds == NULL) + { + DEBUGF("ERROR %s: realloc for _fds failed.", __func__); + exit(-1); + } + + _fds = new_fds; + _fds[_nfds].fd = fd; + _fds[_nfds].events = POLLIN; + + char** new_device_names = realloc(_device_names, sizeof(char*) * (_nfds + 1)); + if(new_device_names == NULL) + { + DEBUGF("ERROR %s: realloc for _device_names failed.", __func__); + exit(-1); + } + + _device_names = new_device_names; + _device_names[_nfds] = strdup(device_name); + if(_device_names[_nfds] == NULL) + { + DEBUGF("ERROR %s: strdup failed.", __func__); + exit(-1); + } + + ++_nfds; + + DEBUGF("DEBUG %s: Opened device %s.", __func__, device_name); +} + + +static void button_init_device(void) +{ + TRACE; + + if((_fds != NULL) || (_nfds != 0) || (_device_names != NULL)) + { + DEBUGF("ERROR %s: Allready initialized.", __func__); + return; + } + + /* The input device directory. */ + static const char device_path[] = "/dev/input"; + + /* Path delimeter. */ + static const char delimeter[] = "/"; + + /* Open all devices in device_path. */ + DIR* dir = opendir(device_path); + if(dir == NULL) + { + DEBUGF("ERROR %s: opendir failed: errno: %d.", __func__, errno); + exit(errno); + } + + char device_name[PATH_MAX]; + strcpy(device_name, device_path); + strcat(device_name, delimeter); + char* device_name_idx = device_name + strlen(device_name); + + struct dirent* dir_entry; + while((dir_entry = readdir(dir))) + { + if( ((dir_entry->d_name[0] == '.') && (dir_entry->d_name[1] == '\0')) + || ((dir_entry->d_name[0] == '.') && (dir_entry->d_name[1] == '.') && (dir_entry->d_name[2] == '\0'))) + { + continue; + } + + strcpy(device_name_idx, dir_entry->d_name); + + /* Open and add device to _fds. */ + open_device(device_name); + } + + closedir(dir); + + /* Sanity check. */ + if(_nfds < 2) + { + DEBUGF("ERROR %s: No input devices.", __func__); + exit(-1); + } +} + + +#define EVENT_TYPE_BUTTON 1 + + +#define EVENT_CODE_BUTTON_PWR_LONG 117 +#define EVENT_CODE_BUTTON_REV 160 +#define EVENT_CODE_BUTTON_NEXT 162 + + +#define EVENT_TYPE_TOUCHSCREEN 3 + + +#define EVENT_CODE_TOUCHSCREEN_X 53 + + +enum user_choice +{ + CHOICE_NONE = -1, + CHOICE_MANGO, + CHOICE_ROCKBOX, + CHOICE_POWEROFF +}; + + +static int get_user_choice(void) +{ + TRACE; + + button_init_device(); + + enum user_choice choice = CHOICE_NONE; + + while(choice == CHOICE_NONE) + { + /* Poll all input devices. */ + poll(_fds, _nfds, 0); + + nfds_t fds_idx = 0; + for( ; fds_idx < _nfds; ++fds_idx) + { + if(! (_fds[fds_idx].revents & POLLIN)) + { + continue; + } + + struct input_event event; + if(read(_fds[fds_idx].fd, &event, sizeof(event)) < (int) sizeof(event)) + { + DEBUGF("ERROR %s: Read of input devices failed.", __func__); + continue; + } + + DEBUGF("DEBUG %s: device: %s, event.type: %d, event.code: %d, event.value: %d", __func__, _device_names[fds_idx], event.type, event.code, event.value); + + if(event.type == EVENT_TYPE_BUTTON) + { + switch(event.code) + { + case EVENT_CODE_BUTTON_REV: + { + choice = CHOICE_MANGO; + break; + } + + case EVENT_CODE_BUTTON_NEXT: + { + choice = CHOICE_ROCKBOX; + break; + } + + case EVENT_CODE_BUTTON_PWR_LONG: + { + choice = CHOICE_POWEROFF; + break; + } + } + } + else if((event.type == EVENT_TYPE_TOUCHSCREEN) && (event.code == EVENT_CODE_TOUCHSCREEN_X)) + { + if(event.value < 160) + { + choice = CHOICE_MANGO; + } + else + { + choice = CHOICE_ROCKBOX; + } + } + } + } + + if(_fds) + { + nfds_t fds_idx = 0; + for( ; fds_idx < _nfds; ++fds_idx) + { + close(_fds[fds_idx].fd); + } + free(_fds); + _fds = NULL; + } + + if(_device_names) + { + nfds_t fds_idx = 0; + for( ; fds_idx < _nfds; ++fds_idx) + { + free(_device_names[fds_idx]); + } + free(_device_names); + _device_names = NULL; + } + + _nfds = 0; + + return choice; +} + + +/* + Changing bit, when hold switch is toggled. + Bit is off when hold switch is engaged. +*/ +#define HOLD_SWITCH_BIT 16 + + +static bool check_for_hold(void) +{ + TRACE; + + char hold_state; + + FILE* f = fopen("/sys/class/axppower/holdkey", "r"); + fscanf(f, "%c", &hold_state); + fclose(f); + + return(! (hold_state & HOLD_SWITCH_BIT)); +} + + +/*- Display -------------------------------------------------------------------------------------*/ + + +static void draw(const char* file) +{ + DEBUGF("DEBUG %s: file: %s.", __func__, file); + + int dev_fd = open("/dev/graphics/fb0", O_RDWR); + if(dev_fd == -1) + { + DEBUGF("ERROR %s: open failed on /dev/graphics/fb0, errno: %d.", __func__, errno); + exit(errno); + } + + /* Get fixed screen information. */ + struct fb_fix_screeninfo finfo; + if(ioctl(dev_fd, FBIOGET_FSCREENINFO, &finfo) < 0) + { + DEBUGF("ERROR %s: ioctl FBIOGET_FSCREENINFO failed on /dev/graphics/fb0, errno: %d.", __func__, errno); + exit(errno); + } + + /* Get the changeable information. */ + struct fb_var_screeninfo vinfo; + if(ioctl(dev_fd, FBIOGET_VSCREENINFO, &vinfo) < 0) + { + DEBUGF("ERROR %s: ioctl FBIOGET_VSCREENINFO failed on /dev/graphics/fb0, errno: %d.", __func__, errno); + exit(errno); + } + + DEBUGF("DEBUG %s: bits_per_pixel: %u, width: %u, height: %u.", __func__, vinfo.bits_per_pixel, vinfo.width, vinfo.height); + + size_t screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; + + /* ToDo: Is this needed? */ + vinfo.xres = 320; + vinfo.xres_virtual = 320; + vinfo.width = 320; + vinfo.yres = 240; + vinfo.yres_virtual = 240; + vinfo.height = 240; + vinfo.xoffset = 0; + vinfo.yoffset = 0; + vinfo.sync = 0; + vinfo.vmode = 0; + vinfo.pixclock = 104377; + vinfo.left_margin = 20; + vinfo.right_margin = 50; + vinfo.upper_margin = 2; + vinfo.lower_margin = 4; + vinfo.hsync_len = 10; + vinfo.vsync_len = 2; + vinfo.red.offset = 11; + vinfo.red.length = 5; + vinfo.red.msb_right = 0; + vinfo.green.offset = 5; + vinfo.green.length = 6; + vinfo.green.msb_right = 0; + vinfo.blue.offset = 0; + vinfo.blue.length = 5; + vinfo.blue.msb_right = 0; + vinfo.transp.offset = 0; + vinfo.transp.length = 0; + vinfo.transp.msb_right = 0; + vinfo.nonstd = 4; + if(ioctl(dev_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0) + { + DEBUGF("ERROR %s: ioctl FBIOPUT_VSCREENINFO failed on /dev/graphics/fb0, errno: %d.", __func__, errno); + exit(errno); + } + + /* Map the device to memory. */ + char* dev_fb = (char*) mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, 0); + if(dev_fb == MAP_FAILED) + { + DEBUGF("ERROR %s: mmap failed on /dev/graphics/fb0, errno: %d.", __func__, errno); + exit(errno); + } + + BMP* bmp = BMP_ReadFile(file); + if(BMP_GetError() != BMP_OK ) + { + DEBUGF("ERROR %s: BMP_ReadFile failed on %s: %d.", __func__, file, BMP_GetError()); + exit(BMP_GetError()); + } + + int y = 0; + for( ; y < 240; ++y) + { + int x = 0; + for( ; x < 320; ++x) + { + long int position = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8 ) + + (y + vinfo.yoffset) * finfo.line_length; + UCHAR r, g, b; + BMP_GetPixelRGB(bmp, x, y, &r, &g, &b); + unsigned short int pixel = (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3); + *((unsigned short int*)(dev_fb + position)) = pixel; + } + } + + BMP_Free(bmp); + munmap(dev_fb, screensize); + close(dev_fd); +} + + +/*-----------------------------------------------------------------------------------------------*/ + + +static const char ROCKBOX_BIN[] = "/mnt/sdcard/.rockbox/rockbox"; +static const char OF_PLAYER_BIN[] = "/system/bin/MangoPlayer_original"; +static const char PLAYER_FILE[] = "/data/chosen_player"; + + +int main(int argc, char **argv) +{ + TRACE; + + /* + Create the iBasso Vold socket and monitor it. + Do this early to not block Vold. + */ + vold_monitor_start(); + + int last_chosen_player = CHOICE_NONE; + + FILE* f = fopen(PLAYER_FILE, "r"); + if(f != NULL) + { + fscanf(f, "%d", &last_chosen_player); + fclose(f); + } + + DEBUGF("DEBUG %s: Current player choice: %d.", __func__, last_chosen_player); + + if(check_for_hold() || (last_chosen_player == CHOICE_NONE)) + { + draw("/system/chooser.bmp"); + + enum user_choice choice = get_user_choice(); + + if(choice == CHOICE_POWEROFF) + { + reboot(RB_POWER_OFF); + while(true) + { + sleep(1); + } + } + + if(choice != last_chosen_player) + { + last_chosen_player = choice; + + f = fopen(PLAYER_FILE, "w"); + fprintf(f, "%d", last_chosen_player); + fclose(f); + } + + DEBUGF("DEBUG %s: New player choice: %d.", __func__, last_chosen_player); + } + + /* true, Rockbox was started at least once. */ + bool rockboxStarted = false; + + while(true) + { + /* Excecute OF MangoPlayer or Rockbox and restart it if it crashes. */ + + if(last_chosen_player == CHOICE_ROCKBOX) + { + if(rockboxStarted) + { + /* + At this point it is assumed, that Rockbox has exited due to a USB connection + triggering a remount of the internal storage for mass storage access. + Rockbox will eventually restart, when /mnt/sdcard becomes available again. + */ + draw("/system/usb.bmp"); + } + + pthread_mutex_lock(&_sdcard_mount_mtx); + while(_sdcard_not_mounted) + { + DEBUGF("DEBUG %s: Waiting on /mnt/sdcard/.", __func__); + + pthread_cond_wait(&_sdcard_mount_cond, &_sdcard_mount_mtx); + + DEBUGF("DEBUG %s: /mnt/sdcard/ available.", __func__); + } + pthread_mutex_unlock(&_sdcard_mount_mtx); + + /* To be able to execute rockbox. */ + system("mount -o remount,exec /mnt/sdcard"); + + /* This symlink is needed mainly to keep themes functional. */ + system("ln -s /mnt/sdcard/.rockbox /.rockbox"); + + if(access(ROCKBOX_BIN, X_OK) != -1) + { + /* Start Rockbox. */ + + /* Rockbox has its own vold monitor. */ + vold_monitor_stop(); + + DEBUGF("DEBUG %s: Excecuting %s.", __func__, ROCKBOX_BIN); + + int ret_code = system(ROCKBOX_BIN); + rockboxStarted = true; + + DEBUGF("DEBUG %s: ret_code: %d.", __func__, ret_code); + + if(WIFEXITED(ret_code) && (WEXITSTATUS(ret_code) == 42)) + { + /* + Rockbox terminated to prevent a froced shutdown due to a USB connection + triggering a remount of the internal storage for mass storage access. + */ + _sdcard_not_mounted = true; + } + /* else Rockbox crashed ... */ + + vold_monitor_start(); + } + else + { + /* Rockbox executable missing. Show info screen for 30 seconds. */ + draw("/system/rbmissing.bmp"); + sleep(30); + + /* Do not block Vold, so stop after sleep. */ + vold_monitor_stop(); + +#ifdef DEBUG + system("setprop persist.sys.usb.config adb"); + system("setprop persist.usb.debug 1"); +#endif + + DEBUGF("DEBUG %s: Rockbox missing, excecuting %s.", __func__, OF_PLAYER_BIN); + + /* Start OF MangoPlayer. */ + int ret_code = system(OF_PLAYER_BIN); + + DEBUGF("DEBUG %s: ret_code: %d.", __func__, ret_code); + } + } + else /* if(last_chosen_player == CHOICE_MANGO) */ + { + vold_monitor_stop(); + + DEBUGF("DEBUG %s: Excecuting %s.", __func__, OF_PLAYER_BIN); + + int ret_code = system(OF_PLAYER_BIN); + + DEBUGF("DEBUG %s: ret_code: %d.", __func__, ret_code); + } + } + + return 0; +} diff --git a/utils/ibassoboot/jni/qdbmp.c b/utils/ibassoboot/jni/qdbmp.c new file mode 100644 index 0000000000..fd1337277d --- /dev/null +++ b/utils/ibassoboot/jni/qdbmp.c @@ -0,0 +1,798 @@ +#include "qdbmp.h" +#include +#include + + +/* Bitmap header */ +typedef struct _BMP_Header +{ + USHORT Magic; /* Magic identifier: "BM" */ + UINT FileSize; /* Size of the BMP file in bytes */ + USHORT Reserved1; /* Reserved */ + USHORT Reserved2; /* Reserved */ + UINT DataOffset; /* Offset of image data relative to the file's start */ + UINT HeaderSize; /* Size of the header in bytes */ + UINT Width; /* Bitmap's width */ + UINT Height; /* Bitmap's height */ + USHORT Planes; /* Number of color planes in the bitmap */ + USHORT BitsPerPixel; /* Number of bits per pixel */ + UINT CompressionType; /* Compression type */ + UINT ImageDataSize; /* Size of uncompressed image's data */ + UINT HPixelsPerMeter; /* Horizontal resolution (pixels per meter) */ + UINT VPixelsPerMeter; /* Vertical resolution (pixels per meter) */ + UINT ColorsUsed; /* Number of color indexes in the color table that are actually used by the bitmap */ + UINT ColorsRequired; /* Number of color indexes that are required for displaying the bitmap */ +} BMP_Header; + + +/* Private data structure */ +struct _BMP +{ + BMP_Header Header; + UCHAR* Palette; + UCHAR* Data; +}; + + +/* Holds the last error code */ +static BMP_STATUS BMP_LAST_ERROR_CODE = 0; + + +/* Error description strings */ +static const char* BMP_ERROR_STRING[] = +{ + "", + "General error", + "Could not allocate enough memory to complete the operation", + "File input/output error", + "File not found", + "File is not a supported BMP variant (must be uncompressed 8, 24 or 32 BPP)", + "File is not a valid BMP image", + "An argument is invalid or out of range", + "The requested action is not compatible with the BMP's type" +}; + + +/* Size of the palette data for 8 BPP bitmaps */ +#define BMP_PALETTE_SIZE ( 256 * 4 ) + + + +/*********************************** Forward declarations **********************************/ +int ReadHeader ( BMP* bmp, FILE* f ); +int WriteHeader ( BMP* bmp, FILE* f ); + +int ReadUINT ( UINT* x, FILE* f ); +int ReadUSHORT ( USHORT *x, FILE* f ); + +int WriteUINT ( UINT x, FILE* f ); +int WriteUSHORT ( USHORT x, FILE* f ); + + + + + + +/*********************************** Public methods **********************************/ + + +/************************************************************** + Creates a blank BMP image with the specified dimensions + and bit depth. +**************************************************************/ +BMP* BMP_Create( UINT width, UINT height, USHORT depth ) +{ + BMP* bmp; + int bytes_per_pixel = depth >> 3; + UINT bytes_per_row; + + if ( height <= 0 || width <= 0 ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return NULL; + } + + if ( depth != 8 && depth != 24 && depth != 32 ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_NOT_SUPPORTED; + return NULL; + } + + + /* Allocate the bitmap data structure */ + bmp = calloc( 1, sizeof( BMP ) ); + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + return NULL; + } + + + /* Set header' default values */ + bmp->Header.Magic = 0x4D42; + bmp->Header.Reserved1 = 0; + bmp->Header.Reserved2 = 0; + bmp->Header.HeaderSize = 40; + bmp->Header.Planes = 1; + bmp->Header.CompressionType = 0; + bmp->Header.HPixelsPerMeter = 0; + bmp->Header.VPixelsPerMeter = 0; + bmp->Header.ColorsUsed = 0; + bmp->Header.ColorsRequired = 0; + + + /* Calculate the number of bytes used to store a single image row. This is always + rounded up to the next multiple of 4. */ + bytes_per_row = width * bytes_per_pixel; + bytes_per_row += ( bytes_per_row % 4 ? 4 - bytes_per_row % 4 : 0 ); + + + /* Set header's image specific values */ + bmp->Header.Width = width; + bmp->Header.Height = height; + bmp->Header.BitsPerPixel = depth; + bmp->Header.ImageDataSize = bytes_per_row * height; + bmp->Header.FileSize = bmp->Header.ImageDataSize + 54 + ( depth == 8 ? BMP_PALETTE_SIZE : 0 ); + bmp->Header.DataOffset = 54 + ( depth == 8 ? BMP_PALETTE_SIZE : 0 ); + + + /* Allocate palette */ + if ( bmp->Header.BitsPerPixel == 8 ) + { + bmp->Palette = (UCHAR*) calloc( BMP_PALETTE_SIZE, sizeof( UCHAR ) ); + if ( bmp->Palette == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + free( bmp ); + return NULL; + } + } + else + { + bmp->Palette = NULL; + } + + + /* Allocate pixels */ + bmp->Data = (UCHAR*) calloc( bmp->Header.ImageDataSize, sizeof( UCHAR ) ); + if ( bmp->Data == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + free( bmp->Palette ); + free( bmp ); + return NULL; + } + + + BMP_LAST_ERROR_CODE = BMP_OK; + + return bmp; +} + + +/************************************************************** + Frees all the memory used by the specified BMP image. +**************************************************************/ +void BMP_Free( BMP* bmp ) +{ + if ( bmp == NULL ) + { + return; + } + + if ( bmp->Palette != NULL ) + { + free( bmp->Palette ); + } + + if ( bmp->Data != NULL ) + { + free( bmp->Data ); + } + + free( bmp ); + + BMP_LAST_ERROR_CODE = BMP_OK; +} + + +/************************************************************** + Reads the specified BMP image file. +**************************************************************/ +BMP* BMP_ReadFile( const char* filename ) +{ + BMP* bmp; + FILE* f; + + if ( filename == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return NULL; + } + + + /* Allocate */ + bmp = calloc( 1, sizeof( BMP ) ); + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + return NULL; + } + + + /* Open file */ + f = fopen( filename, "rb" ); + if ( f == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_NOT_FOUND; + free( bmp ); + return NULL; + } + + + /* Read header */ + if ( ReadHeader( bmp, f ) != BMP_OK || bmp->Header.Magic != 0x4D42 ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_INVALID; + fclose( f ); + free( bmp ); + return NULL; + } + + + /* Verify that the bitmap variant is supported */ + if ( ( bmp->Header.BitsPerPixel != 32 && bmp->Header.BitsPerPixel != 24 && bmp->Header.BitsPerPixel != 8 ) + || bmp->Header.CompressionType != 0 || bmp->Header.HeaderSize != 40 ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_NOT_SUPPORTED; + fclose( f ); + free( bmp ); + return NULL; + } + + + /* Allocate and read palette */ + if ( bmp->Header.BitsPerPixel == 8 ) + { + bmp->Palette = (UCHAR*) malloc( BMP_PALETTE_SIZE * sizeof( UCHAR ) ); + if ( bmp->Palette == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + fclose( f ); + free( bmp ); + return NULL; + } + + if ( fread( bmp->Palette, sizeof( UCHAR ), BMP_PALETTE_SIZE, f ) != BMP_PALETTE_SIZE ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_INVALID; + fclose( f ); + free( bmp->Palette ); + free( bmp ); + return NULL; + } + } + else /* Not an indexed image */ + { + bmp->Palette = NULL; + } + + + /* Allocate memory for image data */ + bmp->Data = (UCHAR*) malloc( bmp->Header.ImageDataSize ); + if ( bmp->Data == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_OUT_OF_MEMORY; + fclose( f ); + free( bmp->Palette ); + free( bmp ); + return NULL; + } + + + /* Read image data */ + if ( fread( bmp->Data, sizeof( UCHAR ), bmp->Header.ImageDataSize, f ) != bmp->Header.ImageDataSize ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_INVALID; + fclose( f ); + free( bmp->Data ); + free( bmp->Palette ); + free( bmp ); + return NULL; + } + + + fclose( f ); + + BMP_LAST_ERROR_CODE = BMP_OK; + + return bmp; +} + + +/************************************************************** + Writes the BMP image to the specified file. +**************************************************************/ +void BMP_WriteFile( BMP* bmp, const char* filename ) +{ + FILE* f; + + if ( filename == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return; + } + + + /* Open file */ + f = fopen( filename, "wb" ); + if ( f == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_FILE_NOT_FOUND; + return; + } + + + /* Write header */ + if ( WriteHeader( bmp, f ) != BMP_OK ) + { + BMP_LAST_ERROR_CODE = BMP_IO_ERROR; + fclose( f ); + return; + } + + + /* Write palette */ + if ( bmp->Palette ) + { + if ( fwrite( bmp->Palette, sizeof( UCHAR ), BMP_PALETTE_SIZE, f ) != BMP_PALETTE_SIZE ) + { + BMP_LAST_ERROR_CODE = BMP_IO_ERROR; + fclose( f ); + return; + } + } + + + /* Write data */ + if ( fwrite( bmp->Data, sizeof( UCHAR ), bmp->Header.ImageDataSize, f ) != bmp->Header.ImageDataSize ) + { + BMP_LAST_ERROR_CODE = BMP_IO_ERROR; + fclose( f ); + return; + } + + + BMP_LAST_ERROR_CODE = BMP_OK; + fclose( f ); +} + + +/************************************************************** + Returns the image's width. +**************************************************************/ +UINT BMP_GetWidth( BMP* bmp ) +{ + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return -1; + } + + BMP_LAST_ERROR_CODE = BMP_OK; + + return ( bmp->Header.Width ); +} + + +/************************************************************** + Returns the image's height. +**************************************************************/ +UINT BMP_GetHeight( BMP* bmp ) +{ + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return -1; + } + + BMP_LAST_ERROR_CODE = BMP_OK; + + return ( bmp->Header.Height ); +} + + +/************************************************************** + Returns the image's color depth (bits per pixel). +**************************************************************/ +USHORT BMP_GetDepth( BMP* bmp ) +{ + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + return -1; + } + + BMP_LAST_ERROR_CODE = BMP_OK; + + return ( bmp->Header.BitsPerPixel ); +} + + +/************************************************************** + Populates the arguments with the specified pixel's RGB + values. +**************************************************************/ +void BMP_GetPixelRGB( BMP* bmp, UINT x, UINT y, UCHAR* r, UCHAR* g, UCHAR* b ) +{ + UCHAR* pixel; + UINT bytes_per_row; + UCHAR bytes_per_pixel; + + if ( bmp == NULL || x < 0 || x >= bmp->Header.Width || y < 0 || y >= bmp->Header.Height ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + else + { + BMP_LAST_ERROR_CODE = BMP_OK; + + bytes_per_pixel = bmp->Header.BitsPerPixel >> 3; + + /* Row's size is rounded up to the next multiple of 4 bytes */ + bytes_per_row = bmp->Header.ImageDataSize / bmp->Header.Height; + + /* Calculate the location of the relevant pixel (rows are flipped) */ + pixel = bmp->Data + ( ( bmp->Header.Height - y - 1 ) * bytes_per_row + x * bytes_per_pixel ); + + + /* In indexed color mode the pixel's value is an index within the palette */ + if ( bmp->Header.BitsPerPixel == 8 ) + { + pixel = bmp->Palette + *pixel * 4; + } + + /* Note: colors are stored in BGR order */ + if ( r ) *r = *( pixel + 2 ); + if ( g ) *g = *( pixel + 1 ); + if ( b ) *b = *( pixel + 0 ); + } +} + + +/************************************************************** + Sets the specified pixel's RGB values. +**************************************************************/ +void BMP_SetPixelRGB( BMP* bmp, UINT x, UINT y, UCHAR r, UCHAR g, UCHAR b ) +{ + UCHAR* pixel; + UINT bytes_per_row; + UCHAR bytes_per_pixel; + + if ( bmp == NULL || x < 0 || x >= bmp->Header.Width || y < 0 || y >= bmp->Header.Height ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + + else if ( bmp->Header.BitsPerPixel != 24 && bmp->Header.BitsPerPixel != 32 ) + { + BMP_LAST_ERROR_CODE = BMP_TYPE_MISMATCH; + } + + else + { + BMP_LAST_ERROR_CODE = BMP_OK; + + bytes_per_pixel = bmp->Header.BitsPerPixel >> 3; + + /* Row's size is rounded up to the next multiple of 4 bytes */ + bytes_per_row = bmp->Header.ImageDataSize / bmp->Header.Height; + + /* Calculate the location of the relevant pixel (rows are flipped) */ + pixel = bmp->Data + ( ( bmp->Header.Height - y - 1 ) * bytes_per_row + x * bytes_per_pixel ); + + /* Note: colors are stored in BGR order */ + *( pixel + 2 ) = r; + *( pixel + 1 ) = g; + *( pixel + 0 ) = b; + } +} + + +/************************************************************** + Gets the specified pixel's color index. +**************************************************************/ +void BMP_GetPixelIndex( BMP* bmp, UINT x, UINT y, UCHAR* val ) +{ + UCHAR* pixel; + UINT bytes_per_row; + + if ( bmp == NULL || x < 0 || x >= bmp->Header.Width || y < 0 || y >= bmp->Header.Height ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + + else if ( bmp->Header.BitsPerPixel != 8 ) + { + BMP_LAST_ERROR_CODE = BMP_TYPE_MISMATCH; + } + + else + { + BMP_LAST_ERROR_CODE = BMP_OK; + + /* Row's size is rounded up to the next multiple of 4 bytes */ + bytes_per_row = bmp->Header.ImageDataSize / bmp->Header.Height; + + /* Calculate the location of the relevant pixel */ + pixel = bmp->Data + ( ( bmp->Header.Height - y - 1 ) * bytes_per_row + x ); + + + if ( val ) *val = *pixel; + } +} + + +/************************************************************** + Sets the specified pixel's color index. +**************************************************************/ +void BMP_SetPixelIndex( BMP* bmp, UINT x, UINT y, UCHAR val ) +{ + UCHAR* pixel; + UINT bytes_per_row; + + if ( bmp == NULL || x < 0 || x >= bmp->Header.Width || y < 0 || y >= bmp->Header.Height ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + + else if ( bmp->Header.BitsPerPixel != 8 ) + { + BMP_LAST_ERROR_CODE = BMP_TYPE_MISMATCH; + } + + else + { + BMP_LAST_ERROR_CODE = BMP_OK; + + /* Row's size is rounded up to the next multiple of 4 bytes */ + bytes_per_row = bmp->Header.ImageDataSize / bmp->Header.Height; + + /* Calculate the location of the relevant pixel */ + pixel = bmp->Data + ( ( bmp->Header.Height - y - 1 ) * bytes_per_row + x ); + + *pixel = val; + } +} + + +/************************************************************** + Gets the color value for the specified palette index. +**************************************************************/ +void BMP_GetPaletteColor( BMP* bmp, UCHAR index, UCHAR* r, UCHAR* g, UCHAR* b ) +{ + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + + else if ( bmp->Header.BitsPerPixel != 8 ) + { + BMP_LAST_ERROR_CODE = BMP_TYPE_MISMATCH; + } + + else + { + if ( r ) *r = *( bmp->Palette + index * 4 + 2 ); + if ( g ) *g = *( bmp->Palette + index * 4 + 1 ); + if ( b ) *b = *( bmp->Palette + index * 4 + 0 ); + + BMP_LAST_ERROR_CODE = BMP_OK; + } +} + + +/************************************************************** + Sets the color value for the specified palette index. +**************************************************************/ +void BMP_SetPaletteColor( BMP* bmp, UCHAR index, UCHAR r, UCHAR g, UCHAR b ) +{ + if ( bmp == NULL ) + { + BMP_LAST_ERROR_CODE = BMP_INVALID_ARGUMENT; + } + + else if ( bmp->Header.BitsPerPixel != 8 ) + { + BMP_LAST_ERROR_CODE = BMP_TYPE_MISMATCH; + } + + else + { + *( bmp->Palette + index * 4 + 2 ) = r; + *( bmp->Palette + index * 4 + 1 ) = g; + *( bmp->Palette + index * 4 + 0 ) = b; + + BMP_LAST_ERROR_CODE = BMP_OK; + } +} + + +/************************************************************** + Returns the last error code. +**************************************************************/ +BMP_STATUS BMP_GetError() +{ + return BMP_LAST_ERROR_CODE; +} + + +/************************************************************** + Returns a description of the last error code. +**************************************************************/ +const char* BMP_GetErrorDescription() +{ + if ( BMP_LAST_ERROR_CODE > 0 && BMP_LAST_ERROR_CODE < BMP_ERROR_NUM ) + { + return BMP_ERROR_STRING[ BMP_LAST_ERROR_CODE ]; + } + else + { + return NULL; + } +} + + + + + +/*********************************** Private methods **********************************/ + + +/************************************************************** + Reads the BMP file's header into the data structure. + Returns BMP_OK on success. +**************************************************************/ +int ReadHeader( BMP* bmp, FILE* f ) +{ + if ( bmp == NULL || f == NULL ) + { + return BMP_INVALID_ARGUMENT; + } + + /* The header's fields are read one by one, and converted from the format's + little endian to the system's native representation. */ + if ( !ReadUSHORT( &( bmp->Header.Magic ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.FileSize ), f ) ) return BMP_IO_ERROR; + if ( !ReadUSHORT( &( bmp->Header.Reserved1 ), f ) ) return BMP_IO_ERROR; + if ( !ReadUSHORT( &( bmp->Header.Reserved2 ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.DataOffset ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.HeaderSize ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.Width ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.Height ), f ) ) return BMP_IO_ERROR; + if ( !ReadUSHORT( &( bmp->Header.Planes ), f ) ) return BMP_IO_ERROR; + if ( !ReadUSHORT( &( bmp->Header.BitsPerPixel ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.CompressionType ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.ImageDataSize ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.HPixelsPerMeter ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.VPixelsPerMeter ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.ColorsUsed ), f ) ) return BMP_IO_ERROR; + if ( !ReadUINT( &( bmp->Header.ColorsRequired ), f ) ) return BMP_IO_ERROR; + + return BMP_OK; +} + + +/************************************************************** + Writes the BMP file's header into the data structure. + Returns BMP_OK on success. +**************************************************************/ +int WriteHeader( BMP* bmp, FILE* f ) +{ + if ( bmp == NULL || f == NULL ) + { + return BMP_INVALID_ARGUMENT; + } + + /* The header's fields are written one by one, and converted to the format's + little endian representation. */ + if ( !WriteUSHORT( bmp->Header.Magic, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.FileSize, f ) ) return BMP_IO_ERROR; + if ( !WriteUSHORT( bmp->Header.Reserved1, f ) ) return BMP_IO_ERROR; + if ( !WriteUSHORT( bmp->Header.Reserved2, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.DataOffset, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.HeaderSize, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.Width, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.Height, f ) ) return BMP_IO_ERROR; + if ( !WriteUSHORT( bmp->Header.Planes, f ) ) return BMP_IO_ERROR; + if ( !WriteUSHORT( bmp->Header.BitsPerPixel, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.CompressionType, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.ImageDataSize, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.HPixelsPerMeter, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.VPixelsPerMeter, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.ColorsUsed, f ) ) return BMP_IO_ERROR; + if ( !WriteUINT( bmp->Header.ColorsRequired, f ) ) return BMP_IO_ERROR; + + return BMP_OK; +} + + +/************************************************************** + Reads a little-endian unsigned int from the file. + Returns non-zero on success. +**************************************************************/ +int ReadUINT( UINT* x, FILE* f ) +{ + UCHAR little[ 4 ]; /* BMPs use 32 bit ints */ + + if ( x == NULL || f == NULL ) + { + return 0; + } + + if ( fread( little, 4, 1, f ) != 1 ) + { + return 0; + } + + *x = ( little[ 3 ] << 24 | little[ 2 ] << 16 | little[ 1 ] << 8 | little[ 0 ] ); + + return 1; +} + + +/************************************************************** + Reads a little-endian unsigned short int from the file. + Returns non-zero on success. +**************************************************************/ +int ReadUSHORT( USHORT *x, FILE* f ) +{ + UCHAR little[ 2 ]; /* BMPs use 16 bit shorts */ + + if ( x == NULL || f == NULL ) + { + return 0; + } + + if ( fread( little, 2, 1, f ) != 1 ) + { + return 0; + } + + *x = ( little[ 1 ] << 8 | little[ 0 ] ); + + return 1; +} + + +/************************************************************** + Writes a little-endian unsigned int to the file. + Returns non-zero on success. +**************************************************************/ +int WriteUINT( UINT x, FILE* f ) +{ + UCHAR little[ 4 ]; /* BMPs use 32 bit ints */ + + little[ 3 ] = (UCHAR)( ( x & 0xff000000 ) >> 24 ); + little[ 2 ] = (UCHAR)( ( x & 0x00ff0000 ) >> 16 ); + little[ 1 ] = (UCHAR)( ( x & 0x0000ff00 ) >> 8 ); + little[ 0 ] = (UCHAR)( ( x & 0x000000ff ) >> 0 ); + + return ( f && fwrite( little, 4, 1, f ) == 1 ); +} + + +/************************************************************** + Writes a little-endian unsigned short int to the file. + Returns non-zero on success. +**************************************************************/ +int WriteUSHORT( USHORT x, FILE* f ) +{ + UCHAR little[ 2 ]; /* BMPs use 16 bit shorts */ + + little[ 1 ] = (UCHAR)( ( x & 0xff00 ) >> 8 ); + little[ 0 ] = (UCHAR)( ( x & 0x00ff ) >> 0 ); + + return ( f && fwrite( little, 2, 1, f ) == 1 ); +} + diff --git a/utils/ibassoboot/jni/qdbmp.h b/utils/ibassoboot/jni/qdbmp.h new file mode 100644 index 0000000000..d6c0e6c452 --- /dev/null +++ b/utils/ibassoboot/jni/qdbmp.h @@ -0,0 +1,133 @@ +#ifndef _BMP_H_ +#define _BMP_H_ + + +/************************************************************** + + QDBMP - Quick n' Dirty BMP + + v1.0.0 - 2007-04-07 + http://qdbmp.sourceforge.net + + + The library supports the following BMP variants: + 1. Uncompressed 32 BPP (alpha values are ignored) + 2. Uncompressed 24 BPP + 3. Uncompressed 8 BPP (indexed color) + + QDBMP is free and open source software, distributed + under the MIT licence. + + Copyright (c) 2007 Chai Braudo (braudo@users.sourceforge.net) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +**************************************************************/ + +#include + + + +/* Type definitions */ +#ifndef UINT + #define UINT unsigned long int +#endif + +#ifndef USHORT + #define USHORT unsigned short +#endif + +#ifndef UCHAR + #define UCHAR unsigned char +#endif + + +/* Version */ +#define QDBMP_VERSION_MAJOR 1 +#define QDBMP_VERSION_MINOR 0 +#define QDBMP_VERSION_PATCH 1 + + +/* Error codes */ +typedef enum +{ + BMP_OK = 0, /* No error */ + BMP_ERROR, /* General error */ + BMP_OUT_OF_MEMORY, /* Could not allocate enough memory to complete the operation */ + BMP_IO_ERROR, /* General input/output error */ + BMP_FILE_NOT_FOUND, /* File not found */ + BMP_FILE_NOT_SUPPORTED, /* File is not a supported BMP variant */ + BMP_FILE_INVALID, /* File is not a BMP image or is an invalid BMP */ + BMP_INVALID_ARGUMENT, /* An argument is invalid or out of range */ + BMP_TYPE_MISMATCH, /* The requested action is not compatible with the BMP's type */ + BMP_ERROR_NUM +} BMP_STATUS; + + +/* Bitmap image */ +typedef struct _BMP BMP; + + + + +/*********************************** Public methods **********************************/ + + +/* Construction/destruction */ +BMP* BMP_Create ( UINT width, UINT height, USHORT depth ); +void BMP_Free ( BMP* bmp ); + + +/* I/O */ +BMP* BMP_ReadFile ( const char* filename ); +void BMP_WriteFile ( BMP* bmp, const char* filename ); + + +/* Meta info */ +UINT BMP_GetWidth ( BMP* bmp ); +UINT BMP_GetHeight ( BMP* bmp ); +USHORT BMP_GetDepth ( BMP* bmp ); + + +/* Pixel access */ +void BMP_GetPixelRGB ( BMP* bmp, UINT x, UINT y, UCHAR* r, UCHAR* g, UCHAR* b ); +void BMP_SetPixelRGB ( BMP* bmp, UINT x, UINT y, UCHAR r, UCHAR g, UCHAR b ); +void BMP_GetPixelIndex ( BMP* bmp, UINT x, UINT y, UCHAR* val ); +void BMP_SetPixelIndex ( BMP* bmp, UINT x, UINT y, UCHAR val ); + + +/* Palette handling */ +void BMP_GetPaletteColor ( BMP* bmp, UCHAR index, UCHAR* r, UCHAR* g, UCHAR* b ); +void BMP_SetPaletteColor ( BMP* bmp, UCHAR index, UCHAR r, UCHAR g, UCHAR b ); + + +/* Error handling */ +BMP_STATUS BMP_GetError (); +const char* BMP_GetErrorDescription (); + + +/* Useful macro that may be used after each BMP operation to check for an error */ +#define BMP_CHECK_ERROR( output_file, return_value ) \ + if ( BMP_GetError() != BMP_OK ) \ + { \ + fprintf( ( output_file ), "BMP error: %s\n", BMP_GetErrorDescription() ); \ + return( return_value ); \ + } \ + +#endif diff --git a/utils/ibassoboot/jni/rbmissing.bmp b/utils/ibassoboot/jni/rbmissing.bmp new file mode 100644 index 0000000000..1e97066d8c Binary files /dev/null and b/utils/ibassoboot/jni/rbmissing.bmp differ diff --git a/utils/ibassoboot/jni/usb.bmp b/utils/ibassoboot/jni/usb.bmp new file mode 100644 index 0000000000..c462de2236 Binary files /dev/null and b/utils/ibassoboot/jni/usb.bmp differ -- cgit v1.2.3