1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-09-16 19:04:45 +00:00

Merge pull request #520 from a740g/image-lib-refactor

Refactor Image library
This commit is contained in:
Samuel Gomes 2024-07-22 06:04:22 +05:30 committed by GitHub
commit 804b5e8587
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 1493 additions and 111 deletions

View file

@ -23,21 +23,14 @@
#include <stdlib.h>
#if defined(IMAGE_DEBUG) && IMAGE_DEBUG > 0
# ifdef _MSC_VER
# define IMAGE_DEBUG_PRINT(_fmt_, ...) fprintf(stderr, "DEBUG: %s:%d:%s(): " _fmt_ "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
# else
# define IMAGE_DEBUG_PRINT(_fmt_, _args_...) fprintf(stderr, "DEBUG: %s:%d:%s(): " _fmt_ "\n", __FILE__, __LINE__, __func__, ##_args_)
# endif
# define IMAGE_DEBUG_PRINT(_fmt_, _args_...) \
fprintf(stderr, "\e[1;37mDEBUG: %s:%d:%s(): \e[1;33m" _fmt_ "\e[1;37m\n", __FILE__, __LINE__, __func__, ##_args_)
# define IMAGE_DEBUG_CHECK(_exp_) \
if (!(_exp_)) \
IMAGE_DEBUG_PRINT("Condition (%s) failed", #_exp_)
IMAGE_DEBUG_PRINT("\e[0;31mCondition (%s) failed", #_exp_)
#else
# ifdef _MSC_VER
# define IMAGE_DEBUG_PRINT(_fmt_, ...) // Don't do anything in release builds
# else
# define IMAGE_DEBUG_PRINT(_fmt_, _args_...) // Don't do anything in release builds
# endif
# define IMAGE_DEBUG_CHECK(_exp_) // Don't do anything in release builds
# define IMAGE_DEBUG_PRINT(_fmt_, _args_...) // Don't do anything in release builds
# define IMAGE_DEBUG_CHECK(_exp_) // Don't do anything in release builds
#endif
// This is returned to the caller if something goes wrong while loading the image
@ -58,7 +51,9 @@ static inline constexpr uint8_t image_get_bgra_alpha(const uint32_t c) { return
static inline constexpr uint32_t image_get_bgra_bgr(const uint32_t c) { return (uint32_t)(c & 0xFFFFFFu); }
static inline constexpr uint32_t image_make_bgra(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) {
static inline constexpr uint32_t image_set_bgra_alpha(const uint32_t c, const uint8_t a = 0xFFu) { return uint32_t(c & 0xFFFFFFu) | (uint32_t(a) << 24); }
static inline constexpr uint32_t image_make_bgra(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a = 0xFFu) {
return (uint32_t)(b) | ((uint32_t)(g) << 8) | ((uint32_t)(r) << 16) | ((uint32_t)(a) << 24);
}

View file

@ -8,12 +8,14 @@
// We need 'qbs' and 'image' structs stuff from here. Stop using this when image and friends are refactored
#include "../../../libqb.h"
// Uncomment this to to print debug messages to stderr
// #define IMAGE_DEBUG 1
// This is not strictly needed. But we'll leave it here for VSCode to do it's magic
#define CLIP_ENABLE_IMAGE 1
#include "clip/clip.h"
#include "clipboard.h"
#include "error_handle.h"
#define IMAGE_DEBUG 0
#include "image.h"
#include "qbs.h"
#include <vector>

View file

@ -1,14 +1,28 @@
IMAGE_SRCS := \
image.cpp
image.cpp \
jo_gif/jo_gif.cpp \
nanosvg/nanosvg.cpp \
pixelscalers/hqx.cpp \
pixelscalers/mmpx.cpp \
pixelscalers/sxbr.cpp \
qoi/qoi.cpp \
sg_curico/sg_curico.cpp \
sg_pcx/sg_pcx.cpp \
stb/stb_image.cpp
IMAGE_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/video/image/%.o,$(IMAGE_SRCS))
$(PATH_INTERNAL_C)/parts/video/image/%.o: $(PATH_INTERNAL_C)/parts/video/image/%.cpp
$(CXX) -O2 $(CXXFLAGS) -DDEPENDENCY_CONSOLE_ONLY -Wall $< -c -o $@
IMAGE_LIB := $(PATH_INTERNAL_C)/parts/video/image/image.a
$(IMAGE_LIB): $(IMAGE_OBJS)
$(AR) rcs $@ $(IMAGE_OBJS)
ifdef DEP_IMAGE_CODEC
EXE_LIBS += $(IMAGE_OBJS)
EXE_LIBS += $(IMAGE_LIB)
endif
CLEAN_LIST += $(IMAGE_OBJS)
CLEAN_LIST += $(IMAGE_OBJS) $(IMAGE_LIB)

View file

@ -12,38 +12,32 @@
// qoi (https://qoiformat.org)
// pixelscalers (https://github.com/janert/pixelscalers)
// mmpx (https://github.com/ITotalJustice/mmpx)
// jo_gif (https://www.jonolick.com)
//
//-----------------------------------------------------------------------------------------------------
// Set this to 1 if we want to print debug messages to stderr
#define IMAGE_DEBUG 0
// Uncomment this to to print debug messages to stderr
// #define IMAGE_DEBUG 1
#include "image.h"
// We need 'qbs' and 'image' structs stuff from here
// This should eventually change when things are moved to smaller, logical and self-contained files
#include "../../../libqb.h"
#include "error_handle.h"
#include "filepath.h"
#include "jo_gif/jo_gif.h"
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
#include "pixelscalers/pixelscalers.h"
#include "qbs.h"
#include "qoi/qoi.h"
#include "sg_curico/sg_curico.h"
#include "sg_pcx/sg_pcx.h"
#include "stb/stb_image.h"
#include "stb/stb_image_write.h"
#include <algorithm>
#include <cctype>
#include <string>
#include <unordered_map>
#include <vector>
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"
#define QOI_IMPLEMENTATION
#include "qoi.h"
#define NANOSVG_ALL_COLOR_KEYWORDS
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
#include "sg_pcx.hpp"
#define SXBR_IMPLEMENTATION
#include "pixelscalers/sxbr.hpp"
#define MMPX_IMPLEMENTATION
#include "pixelscalers/mmpx.hpp"
#define HQX_IMPLEMENTATION
#include "pixelscalers/hqx.hpp"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"
#ifdef QB64_WINDOWS
# define ZERO_VARIABLE(_v_) ZeroMemory(&(_v_), sizeof(_v_))
@ -176,9 +170,18 @@ static uint32_t *image_svg_load_from_file(const char *fileName, int32_t *xOut, i
if (!fp)
return nullptr;
fseek(fp, 0, SEEK_END);
if (fseek(fp, 0, SEEK_END)) {
fclose(fp);
return nullptr;
}
auto size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (size < 0) {
fclose(fp);
return nullptr;
}
rewind(fp);
auto svgString = (char *)malloc(size + 1);
if (!svgString) {
@ -311,8 +314,13 @@ static uint32_t *image_decode_from_file(const char *fileName, int32_t *xOut, int
pixels = image_svg_load_from_file(fileName, xOut, yOut, scaler, &compOut, &isVG);
IMAGE_DEBUG_PRINT("Image dimensions (nanosvg) = (%i, %i)", *xOut, *yOut);
if (!pixels)
return nullptr; // Return NULL if all attempts failed
if (!pixels) {
pixels = curico_load_file(fileName, xOut, yOut, &compOut);
IMAGE_DEBUG_PRINT("Image dimensions (sg_curico) = (%i, %i)", *xOut, *yOut);
if (!pixels)
return nullptr; // Return NULL if all attempts failed
}
}
}
}
@ -354,8 +362,13 @@ static uint32_t *image_decode_from_memory(const uint8_t *data, size_t size, int3
pixels = image_svg_load_from_memory(data, size, xOut, yOut, scaler, &compOut, &isVG);
IMAGE_DEBUG_PRINT("Image dimensions (nanosvg) = (%i, %i)", *xOut, *yOut);
if (!pixels)
return nullptr; // Return NULL if all attempts failed
if (!pixels) {
pixels = curico_load_memory(data, size, xOut, yOut, &compOut);
IMAGE_DEBUG_PRINT("Image dimensions (sg_curico) = (%i, %i)", *xOut, *yOut);
if (!pixels)
return nullptr; // Return NULL if all attempts failed
}
}
}
}
@ -684,8 +697,8 @@ int32_t func__loadimage(qbs *qbsFileName, int32_t bpp, qbs *qbsRequirements, int
/// @param qbsRequirements Optional: Extra format and setting arguments
/// @param passed Optional parameters
void sub__saveimage(qbs *qbsFileName, int32_t imageHandle, qbs *qbsRequirements, int32_t passed) {
enum struct SaveFormat { PNG = 0, QOI, BMP, TGA, JPG, HDR };
static const char *formatName[] = {"png", "qoi", "bmp", "tga", "jpg", "hdr"};
enum struct SaveFormat { PNG = 0, QOI, BMP, TGA, JPG, HDR, GIF, ICO };
static const char *formatName[] = {"png", "qoi", "bmp", "tga", "jpg", "hdr", "gif", "ico"};
if (new_error) // leave if there was an error
return;
@ -943,6 +956,24 @@ void sub__saveimage(qbs *qbsFileName, int32_t imageHandle, qbs *qbsRequirements,
}
} break;
case SaveFormat::GIF: {
auto gif = jo_gif_start(fileName.c_str(), short(width), short(height), 0, 255);
if (gif.fp) {
jo_gif_frame(&gif, reinterpret_cast<unsigned char *>(pixels.data()), 0, false);
jo_gif_end(&gif);
} else {
IMAGE_DEBUG_PRINT("jo_gif_start() failed");
error(QB_ERROR_ILLEGAL_FUNCTION_CALL);
}
} break;
case SaveFormat::ICO: {
if (!curico_save_file(fileName.c_str(), width, height, sizeof(uint32_t), pixels.data())) {
IMAGE_DEBUG_PRINT("curico_save_file() failed");
error(QB_ERROR_ILLEGAL_FUNCTION_CALL);
}
} break;
default:
IMAGE_DEBUG_PRINT("Save handler not implemented");
error(QB_ERROR_INTERNAL_ERROR);

View file

@ -0,0 +1,399 @@
/* public domain, Simple, Minimalistic GIF writer - http://jonolick.com
*
* Quick Notes:
* Supports only 4 component input, alpha is currently ignored. (RGBX)
*
* Latest revisions:
* 1.00 (2015-11-03) initial release
*
* Basic usage:
* char *frame = new char[128*128*4]; // 4 component. RGBX format, where X is unused
* jo_gif_t gif = jo_gif_start("foo.gif", 128, 128, 0, 32);
* jo_gif_frame(&gif, frame, 4, false); // frame 1
* jo_gif_frame(&gif, frame, 4, false); // frame 2
* jo_gif_frame(&gif, frame, 4, false); // frame 3, ...
* jo_gif_end(&gif);
* */
#ifndef JO_INCLUDE_GIF_H
#define JO_INCLUDE_GIF_H
#include <stdio.h>
// To get a header file for this, either cut and paste the header,
// or create jo_gif.h, #define JO_GIF_HEADER_FILE_ONLY, and
// then include jo_gif.cpp from it.
typedef struct {
FILE *fp;
unsigned char palette[0x300];
short width, height, repeat;
int numColors, palSize;
int frame;
} jo_gif_t;
// width/height | the same for every frame
// repeat | 0 = loop forever, 1 = loop once, etc...
// palSize | must be power of 2 - 1. so, 255 not 256.
extern jo_gif_t jo_gif_start(const char *filename, short width, short height, short repeat, int palSize);
// gif | the state (returned from jo_gif_start)
// rgba | the pixels
// delayCsec | amount of time in between frames (in centiseconds)
// localPalette | true if you want a unique palette generated for this frame (does not effect future frames)
extern void jo_gif_frame(jo_gif_t *gif, unsigned char *rgba, short delayCsec, bool localPalette);
// gif | the state (returned from jo_gif_start)
extern void jo_gif_end(jo_gif_t *gif);
#endif
#ifndef JO_GIF_HEADER_FILE_ONLY
#if defined(_MSC_VER) && _MSC_VER >= 0x1400
# define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
#endif
#include <math.h>
#include <memory.h>
#include <stdlib.h>
// Based on NeuQuant algorithm
static void jo_gif_quantize(unsigned char *rgba, int rgbaSize, int sample, unsigned char *map, int numColors) {
// defs for freq and bias
const int intbiasshift = 16; /* bias for fractions */
const int intbias = (((int)1) << intbiasshift);
const int gammashift = 10; /* gamma = 1024 */
const int betashift = 10;
const int beta = (intbias >> betashift); /* beta = 1/1024 */
const int betagamma = (intbias << (gammashift - betashift));
// defs for decreasing radius factor
const int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
const int radiusbias = (((int)1) << radiusbiasshift);
const int radiusdec = 30; /* factor of 1/30 each cycle */
// defs for decreasing alpha factor
const int alphabiasshift = 10; /* alpha starts at 1.0 */
const int initalpha = (((int)1) << alphabiasshift);
// radbias and alpharadbias used for radpower calculation
const int radbiasshift = 8;
const int radbias = (((int)1) << radbiasshift);
const int alpharadbshift = (alphabiasshift + radbiasshift);
const int alpharadbias = (((int)1) << alpharadbshift);
sample = sample < 1 ? 1 : sample > 30 ? 30 : sample;
int network[256][3];
int bias[256] = {}, freq[256];
for (int i = 0; i < numColors; ++i) {
// Put nurons evenly through the luminance spectrum.
network[i][0] = network[i][1] = network[i][2] = (i << 12) / numColors;
freq[i] = intbias / numColors;
}
// Learn
{
const int primes[5] = {499, 491, 487, 503};
int step = 4;
for (int i = 0; i < 4; ++i) {
if (rgbaSize > primes[i] * 4 && (rgbaSize % primes[i])) { // TODO/Error? primes[i]*4?
step = primes[i] * 4;
}
}
sample = step == 4 ? 1 : sample;
int alphadec = 30 + ((sample - 1) / 3);
int samplepixels = rgbaSize / (4 * sample);
int delta = samplepixels / 100;
int alpha = initalpha;
delta = delta == 0 ? 1 : delta;
int radius = (numColors >> 3) * radiusbias;
int rad = radius >> radiusbiasshift;
rad = rad <= 1 ? 0 : rad;
int radSq = rad * rad;
int radpower[32];
for (int i = 0; i < rad; i++) {
radpower[i] = alpha * (((radSq - i * i) * radbias) / radSq);
}
// Randomly walk through the pixels and relax neurons to the "optimal" target.
for (int i = 0, pix = 0; i < samplepixels;) {
int r = rgba[pix + 0] << 4;
int g = rgba[pix + 1] << 4;
int b = rgba[pix + 2] << 4;
int j = -1;
{
// finds closest neuron (min dist) and updates freq
// finds best neuron (min dist-bias) and returns position
// for frequently chosen neurons, freq[k] is high and bias[k] is negative
// bias[k] = gamma*((1/numColors)-freq[k])
int bestd = 0x7FFFFFFF, bestbiasd = 0x7FFFFFFF, bestpos = -1;
for (int k = 0; k < numColors; k++) {
int *n = network[k];
int dist = abs(n[0] - r) + abs(n[1] - g) + abs(n[2] - b);
if (dist < bestd) {
bestd = dist;
bestpos = k;
}
int biasdist = dist - ((bias[k]) >> (intbiasshift - 4));
if (biasdist < bestbiasd) {
bestbiasd = biasdist;
j = k;
}
int betafreq = freq[k] >> betashift;
freq[k] -= betafreq;
bias[k] += betafreq << gammashift;
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
}
// Move neuron j towards biased (b,g,r) by factor alpha
network[j][0] -= (network[j][0] - r) * alpha / initalpha;
network[j][1] -= (network[j][1] - g) * alpha / initalpha;
network[j][2] -= (network[j][2] - b) * alpha / initalpha;
if (rad != 0) {
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
int lo = j - rad;
lo = lo < -1 ? -1 : lo;
int hi = j + rad;
hi = hi > numColors ? numColors : hi;
for (int jj = j + 1, m = 1; jj < hi; ++jj) {
int a = radpower[m++];
network[jj][0] -= (network[jj][0] - r) * a / alpharadbias;
network[jj][1] -= (network[jj][1] - g) * a / alpharadbias;
network[jj][2] -= (network[jj][2] - b) * a / alpharadbias;
}
for (int k = j - 1, m = 1; k > lo; --k) {
int a = radpower[m++];
network[k][0] -= (network[k][0] - r) * a / alpharadbias;
network[k][1] -= (network[k][1] - g) * a / alpharadbias;
network[k][2] -= (network[k][2] - b) * a / alpharadbias;
}
}
pix += step;
pix = pix >= rgbaSize ? pix - rgbaSize : pix;
// every 1% of the image, move less over the following iterations.
if (++i % delta == 0) {
alpha -= alpha / alphadec;
radius -= radius / radiusdec;
rad = radius >> radiusbiasshift;
rad = rad <= 1 ? 0 : rad;
radSq = rad * rad;
for (j = 0; j < rad; j++) {
radpower[j] = alpha * ((radSq - j * j) * radbias / radSq);
}
}
}
}
// Unbias network to give byte values 0..255
for (int i = 0; i < numColors; i++) {
map[i * 3 + 0] = network[i][0] >>= 4;
map[i * 3 + 1] = network[i][1] >>= 4;
map[i * 3 + 2] = network[i][2] >>= 4;
}
}
typedef struct {
FILE *fp;
int numBits;
unsigned char buf[256];
unsigned char idx;
unsigned tmp;
int outBits;
int curBits;
} jo_gif_lzw_t;
static void jo_gif_lzw_write(jo_gif_lzw_t *s, int code) {
s->outBits |= code << s->curBits;
s->curBits += s->numBits;
while (s->curBits >= 8) {
s->buf[s->idx++] = s->outBits & 255;
s->outBits >>= 8;
s->curBits -= 8;
if (s->idx >= 255) {
putc(s->idx, s->fp);
fwrite(s->buf, s->idx, 1, s->fp);
s->idx = 0;
}
}
}
static void jo_gif_lzw_encode(unsigned char *in, int len, FILE *fp) {
jo_gif_lzw_t state = {fp, 9};
int maxcode = 511;
// Note: 30k stack space for dictionary =|
const int hashSize = 5003;
short codetab[hashSize];
int hashTbl[hashSize];
memset(hashTbl, 0xFF, sizeof(hashTbl));
jo_gif_lzw_write(&state, 0x100);
int free_ent = 0x102;
int ent = *in++;
CONTINUE:
while (--len) {
int c = *in++;
int fcode = (c << 12) + ent;
int key = (c << 4) ^ ent; // xor hashing
while (hashTbl[key] >= 0) {
if (hashTbl[key] == fcode) {
ent = codetab[key];
goto CONTINUE;
}
++key;
key = key >= hashSize ? key - hashSize : key;
}
jo_gif_lzw_write(&state, ent);
ent = c;
if (free_ent < 4096) {
if (free_ent > maxcode) {
++state.numBits;
if (state.numBits == 12) {
maxcode = 4096;
} else {
maxcode = (1 << state.numBits) - 1;
}
}
codetab[key] = free_ent++;
hashTbl[key] = fcode;
} else {
memset(hashTbl, 0xFF, sizeof(hashTbl));
free_ent = 0x102;
jo_gif_lzw_write(&state, 0x100);
state.numBits = 9;
maxcode = 511;
}
}
jo_gif_lzw_write(&state, ent);
jo_gif_lzw_write(&state, 0x101);
jo_gif_lzw_write(&state, 0);
if (state.idx) {
putc(state.idx, fp);
fwrite(state.buf, state.idx, 1, fp);
}
}
static int jo_gif_clamp(int a, int b, int c) { return a < b ? b : a > c ? c : a; }
jo_gif_t jo_gif_start(const char *filename, short width, short height, short repeat, int numColors) {
numColors = numColors > 255 ? 255 : numColors < 2 ? 2 : numColors;
jo_gif_t gif = {};
gif.width = width;
gif.height = height;
gif.repeat = repeat;
gif.numColors = numColors;
gif.palSize = log2(numColors);
gif.fp = fopen(filename, "wb");
if (!gif.fp) {
// printf("Error: Could not WriteGif to %s\n", filename);
return gif;
}
fwrite("GIF89a", 6, 1, gif.fp);
// Logical Screen Descriptor
fwrite(&gif.width, 2, 1, gif.fp);
fwrite(&gif.height, 2, 1, gif.fp);
putc(0xF0 | gif.palSize, gif.fp);
fwrite("\x00\x00", 2, 1, gif.fp); // bg color index (unused), aspect ratio
return gif;
}
void jo_gif_frame(jo_gif_t *gif, unsigned char *rgba, short delayCsec, bool localPalette) {
if (!gif->fp) {
return;
}
short width = gif->width;
short height = gif->height;
int size = width * height;
unsigned char localPalTbl[0x300];
unsigned char *palette = gif->frame == 0 || !localPalette ? gif->palette : localPalTbl;
if (gif->frame == 0 || localPalette) {
jo_gif_quantize(rgba, size * 4, 1, palette, gif->numColors);
}
unsigned char *indexedPixels = (unsigned char *)malloc(size);
{
unsigned char *ditheredPixels = (unsigned char *)malloc(size * 4);
memcpy(ditheredPixels, rgba, size * 4);
for (int k = 0; k < size * 4; k += 4) {
int rgb[3] = {ditheredPixels[k + 0], ditheredPixels[k + 1], ditheredPixels[k + 2]};
int bestd = 0x7FFFFFFF, best = -1;
// TODO: exhaustive search. do something better.
for (int i = 0; i < gif->numColors; ++i) {
int bb = palette[i * 3 + 0] - rgb[0];
int gg = palette[i * 3 + 1] - rgb[1];
int rr = palette[i * 3 + 2] - rgb[2];
int d = bb * bb + gg * gg + rr * rr;
if (d < bestd) {
bestd = d;
best = i;
}
}
indexedPixels[k / 4] = best;
int diff[3] = {ditheredPixels[k + 0] - palette[indexedPixels[k / 4] * 3 + 0], ditheredPixels[k + 1] - palette[indexedPixels[k / 4] * 3 + 1],
ditheredPixels[k + 2] - palette[indexedPixels[k / 4] * 3 + 2]};
// Floyd-Steinberg Error Diffusion
// TODO: Use something better -- http://caca.zoy.org/study/part3.html
if (k + 4 < size * 4) {
ditheredPixels[k + 4 + 0] = (unsigned char)jo_gif_clamp(ditheredPixels[k + 4 + 0] + (diff[0] * 7 / 16), 0, 255);
ditheredPixels[k + 4 + 1] = (unsigned char)jo_gif_clamp(ditheredPixels[k + 4 + 1] + (diff[1] * 7 / 16), 0, 255);
ditheredPixels[k + 4 + 2] = (unsigned char)jo_gif_clamp(ditheredPixels[k + 4 + 2] + (diff[2] * 7 / 16), 0, 255);
}
if (k + width * 4 + 4 < size * 4) {
for (int i = 0; i < 3; ++i) {
ditheredPixels[k - 4 + width * 4 + i] = (unsigned char)jo_gif_clamp(ditheredPixels[k - 4 + width * 4 + i] + (diff[i] * 3 / 16), 0, 255);
ditheredPixels[k + width * 4 + i] = (unsigned char)jo_gif_clamp(ditheredPixels[k + width * 4 + i] + (diff[i] * 5 / 16), 0, 255);
ditheredPixels[k + width * 4 + 4 + i] = (unsigned char)jo_gif_clamp(ditheredPixels[k + width * 4 + 4 + i] + (diff[i] * 1 / 16), 0, 255);
}
}
}
free(ditheredPixels);
}
if (gif->frame == 0) {
// Global Color Table
fwrite(palette, 3 * (1 << (gif->palSize + 1)), 1, gif->fp);
if (gif->repeat >= 0) {
// Netscape Extension
fwrite("\x21\xff\x0bNETSCAPE2.0\x03\x01", 16, 1, gif->fp);
fwrite(&gif->repeat, 2, 1, gif->fp); // loop count (extra iterations, 0=repeat forever)
putc(0, gif->fp); // block terminator
}
}
// Graphic Control Extension
fwrite("\x21\xf9\x04\x00", 4, 1, gif->fp);
fwrite(&delayCsec, 2, 1, gif->fp); // delayCsec x 1/100 sec
fwrite("\x00\x00", 2, 1, gif->fp); // transparent color index (first byte), currently unused
// Image Descriptor
fwrite("\x2c\x00\x00\x00\x00", 5, 1, gif->fp); // header, x,y
fwrite(&width, 2, 1, gif->fp);
fwrite(&height, 2, 1, gif->fp);
if (gif->frame == 0 || !localPalette) {
putc(0, gif->fp);
} else {
putc(0x80 | gif->palSize, gif->fp);
fwrite(palette, 3 * (1 << (gif->palSize + 1)), 1, gif->fp);
}
putc(8, gif->fp); // block terminator
jo_gif_lzw_encode(indexedPixels, size, gif->fp);
putc(0, gif->fp); // block terminator
++gif->frame;
free(indexedPixels);
}
void jo_gif_end(jo_gif_t *gif) {
if (!gif->fp) {
return;
}
putc(0x3b, gif->fp); // gif trailer
fclose(gif->fp);
}
#endif

View file

@ -0,0 +1,4 @@
#pragma once
#define JO_GIF_HEADER_FILE_ONLY
#include "jo_gif.cpp"

View file

@ -0,0 +1,5 @@
#define NANOSVG_ALL_COLOR_KEYWORDS
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"

View file

@ -17,20 +17,8 @@
* and modified by Philipp K. Janert, September 2022
*/
#ifndef __JANERT_PIXELSCALERS_HQX__
#define __JANERT_PIXELSCALERS_HQX__
#include <cstdint>
void hq2xA(uint32_t *img, int w, int h, uint32_t *out);
void hq2xB(uint32_t *img, int w, int h, uint32_t *out);
void hq3xA(uint32_t *img, int w, int h, uint32_t *out);
void hq3xB(uint32_t *img, int w, int h, uint32_t *out);
#endif
#ifdef HQX_IMPLEMENTATION
#include <cstdlib>
#define MASK_RB 0x00FF00FF
#define MASK_G 0x0000FF00
@ -4985,5 +4973,3 @@ void hq2xB(uint32_t *img, int w, int h, uint32_t *out) { hq2x_resize('B', img, w
void hq3xA(uint32_t *img, int w, int h, uint32_t *out) { hq3x_resize('A', img, w, h, out, 0x30, 0x07, 0x06, 0x50, false, false); }
void hq3xB(uint32_t *img, int w, int h, uint32_t *out) { hq3x_resize('B', img, w, h, out, 0x30, 0x07, 0x06, 0x50, false, false); }
#endif

View file

@ -4,18 +4,8 @@
https://casual-effects.com/research/McGuire2021PixelArt/index.html
*/
#ifndef __MMPX_H__
#define __MMPX_H__
#include <cstdint>
void mmpx_scale2x(const uint32_t *srcBuffer, uint32_t *dst, uint32_t srcWidth, uint32_t srcHeight);
#endif
#ifdef MMPX_IMPLEMENTATION
#include <cstdbool>
#include <cstdint>
static inline uint32_t luma(uint32_t color) {
const uint32_t alpha = (color & 0xFF000000) >> 24;
@ -159,7 +149,7 @@ void mmpx_scale2x(const uint32_t *srcBuffer, uint32_t *dst, uint32_t srcWidth, u
M = K;
}
} // F !== D
} // 2:1 slope
} // 2:1 slope
}
const uint32_t dstIndex = ((srcX + srcX) + (srcY << 2) * srcWidth) >> 0;
@ -185,7 +175,5 @@ void mmpx_scale2x(const uint32_t *srcBuffer, uint32_t *dst, uint32_t srcWidth, u
H = I;
I = src(&meta, srcX + 2, srcY + 1);
} // X
} // Y
} // Y
}
#endif

View file

@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
void hq2xA(uint32_t *img, int w, int h, uint32_t *out);
void hq2xB(uint32_t *img, int w, int h, uint32_t *out);
void hq3xA(uint32_t *img, int w, int h, uint32_t *out);
void hq3xB(uint32_t *img, int w, int h, uint32_t *out);
void mmpx_scale2x(const uint32_t *srcBuffer, uint32_t *dst, uint32_t srcWidth, uint32_t srcHeight);
void scaleSuperXBR2(uint32_t *data, int w, int h, uint32_t *out);

View file

@ -29,21 +29,12 @@ by Philipp K. Janert, September 2022
*/
#ifndef __JANERT_PIXELSCALERS_XBR__
#define __JANERT_PIXELSCALERS_XBR__
#include <cstdint>
void scaleSuperXBR2(uint32_t *data, int w, int h, uint32_t *out);
#endif
#ifdef SXBR_IMPLEMENTATION
//// *** Super-xBR code begins here - MIT LICENSE *** ///
// PKJ:
#include <algorithm>
#include <cmath>
#include <cstdint>
#define u32 uint32_t
@ -391,5 +382,3 @@ template <int f> void scaleSuperXBRT(u32 *data, u32 *out, int w, int h) {
//// *** Super-xBR code ends here - MIT LICENSE *** ///
void scaleSuperXBR2(uint32_t *data, int w, int h, uint32_t *out) { scaleSuperXBRT<2>(data, out, w, h); }
#endif

View file

@ -0,0 +1,2 @@
#define QOI_IMPLEMENTATION
#include "qoi.h"

View file

@ -0,0 +1,717 @@
//-----------------------------------------------------------------------------------------------------
// Windows Cursor & Icon Loader for QB64-PE by a740g
//
// Bibliography:
// https://en.wikipedia.org/wiki/ICO_(file_format)
// https://learn.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10)
// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
// https://devblogs.microsoft.com/oldnewthing/20101019-00/?p=12503
// https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473
// https://books.google.co.in/books?id=LpkFEO2FG8sC&pg=PA318&redir_esc=y#v=onepage&q&f=false
// https://books.google.co.in/books?id=qR6ngUchllsC&pg=PA349&redir_esc=y#v=onepage&q&f=false
// https://www.informit.com/articles/article.aspx?p=1186882
//-----------------------------------------------------------------------------------------------------
// Uncomment this to to print debug messages to stderr
// #define IMAGE_DEBUG 1
#include "sg_curico.h"
#include "../stb/stb_image.h"
#include "../stb/stb_image_write.h"
#include "image.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class CurIcoImage {
class Stream {
const uint8_t *buffer;
size_t size;
size_t cursor;
auto Read(uint8_t *data, size_t dataSize) {
auto bytesToRead = std::min(dataSize, size - cursor);
if (bytesToRead > 0) {
std::copy(buffer + cursor, buffer + cursor + bytesToRead, data);
cursor += bytesToRead;
}
return bytesToRead;
}
public:
Stream(const uint8_t *data, size_t dataSize) : buffer(data), size(dataSize), cursor(0) {}
auto IsEOF() const { return cursor >= size; }
auto GetSize() const { return size; }
auto GetPosition() const { return cursor; }
void Seek(size_t position) {
if (position <= size)
cursor = position;
else
throw std::runtime_error("Failed to seek to position " + std::to_string(position) + " of " + std::to_string(size));
}
template <typename T> T Read() {
T value = T();
if (Read(reinterpret_cast<uint8_t *>(&value), sizeof(T)) != sizeof(T))
throw std::runtime_error("Failed to read " + std::to_string(sizeof(T)) + " byte(s) from position " + std::to_string(cursor) + " of " +
std::to_string(size));
return value;
}
auto GetData() { return buffer + cursor; }
};
enum class Type : uint16_t {
Icon = 1,
Cursor,
};
struct Header {
uint16_t reserved;
Type type;
uint16_t count;
Header() : reserved(0), type(Type::Icon), count(0) {}
Header(Type type) : reserved(0), type(type), count(0) {}
Header(Stream &stream) { ReadFromStream(stream); }
auto IsValid() { return reserved == 0 && (type == Type::Icon || type == Type::Cursor) && count > 0; }
static auto GetStructSize() noexcept { return 6; } // sizeof(Header)
void ReadFromStream(Stream &stream) {
reserved = stream.Read<uint16_t>();
type = Type(stream.Read<uint16_t>());
count = stream.Read<uint16_t>();
}
void WriteToFile(FILE *file) const noexcept {
fwrite(&reserved, sizeof(reserved), 1, file);
fwrite(&type, sizeof(type), 1, file);
fwrite(&count, sizeof(count), 1, file);
}
};
struct DirectoryEntry {
uint8_t width;
uint8_t height;
uint8_t colorCount;
uint8_t reserved;
uint16_t planes_xHotspot;
uint16_t bitCount_yHotspot;
uint32_t bytesInRes;
uint32_t imageOffset;
DirectoryEntry() : width(0), height(0), colorCount(0), reserved(0), planes_xHotspot(0), bitCount_yHotspot(0), bytesInRes(0), imageOffset(0) {}
static auto GetStructSize() noexcept { return 16; } // sizeof(DirectoryEntry)
void ReadFromStream(Stream &stream) {
width = stream.Read<uint8_t>();
height = stream.Read<uint8_t>();
colorCount = stream.Read<uint8_t>();
reserved = stream.Read<uint8_t>();
planes_xHotspot = stream.Read<uint16_t>();
bitCount_yHotspot = stream.Read<uint16_t>();
bytesInRes = stream.Read<uint32_t>();
imageOffset = stream.Read<uint32_t>();
}
void WriteToFile(FILE *file) const noexcept {
fwrite(&width, sizeof(width), 1, file);
fwrite(&height, sizeof(height), 1, file);
fwrite(&colorCount, sizeof(colorCount), 1, file);
fwrite(&reserved, sizeof(reserved), 1, file);
fwrite(&planes_xHotspot, sizeof(planes_xHotspot), 1, file);
fwrite(&bitCount_yHotspot, sizeof(bitCount_yHotspot), 1, file);
fwrite(&bytesInRes, sizeof(bytesInRes), 1, file);
fwrite(&imageOffset, sizeof(imageOffset), 1, file);
}
};
struct BmpInfoHeader {
uint32_t structSize;
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bitCount;
uint32_t compression;
uint32_t sizeImage;
int32_t xPelsPerMeter;
int32_t yPelsPerMeter;
uint32_t clrUsed;
uint32_t clrImportant;
BmpInfoHeader()
: structSize(sizeof(BmpInfoHeader)), width(0), height(0), planes(0), bitCount(0), compression(0), sizeImage(0), xPelsPerMeter(0), yPelsPerMeter(0),
clrUsed(0), clrImportant(0) {}
BmpInfoHeader(uint32_t width, uint32_t height, uint32_t bpp)
: structSize(sizeof(BmpInfoHeader)), width(width), height(height), planes(0), bitCount(0), compression(0), sizeImage(width * height * bpp / 8),
xPelsPerMeter(0), yPelsPerMeter(0), clrUsed(0), clrImportant(0) {}
static auto GetStructSize() noexcept { return 40; } // sizeof(BmpInfoHeader)
void ReadFromStream(Stream &stream) {
structSize = stream.Read<uint32_t>();
width = stream.Read<int32_t>();
height = stream.Read<int32_t>();
planes = stream.Read<uint16_t>();
bitCount = stream.Read<uint16_t>();
compression = stream.Read<uint32_t>();
sizeImage = stream.Read<uint32_t>();
xPelsPerMeter = stream.Read<int32_t>();
yPelsPerMeter = stream.Read<int32_t>();
clrUsed = stream.Read<uint32_t>();
clrImportant = stream.Read<uint32_t>();
}
};
class Color {
public:
union BGRA32 {
struct Tuple {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
} tuple;
uint32_t value;
} color;
Color() { color.value = 0; }
Color(uint32_t value) { color.value = value; }
Color(uint8_t b, uint8_t g, uint8_t r, uint8_t a = 0xFFu) { SetFromComponents(b, g, r, a); }
void SetFromComponents(uint8_t b, uint8_t g, uint8_t r, uint8_t a = 0xFFu) {
color.tuple.b = b;
color.tuple.g = g;
color.tuple.r = r;
color.tuple.a = a;
}
};
class Palette {
std::vector<Color> palette;
public:
Palette() = default;
Palette(size_t size) { Resize(size); }
Palette(Stream &input, size_t size) { ReadFromStream(input, size); }
auto GetSize() { return palette.size(); }
auto GetColor(size_t index) { return palette[index]; }
void SetColor(size_t index, Color value) { palette[index] = value; }
void Resize(size_t size) {
if (size > 256)
throw std::runtime_error("Unsupported palette size: " + std::to_string(size));
palette.resize(size);
}
void ReadFromStream(Stream &input, size_t size) {
Resize(size);
for (size_t i = 0; i < palette.size(); i++) {
Color entry;
// WARNING: The loading order is important
entry.color.tuple.r = input.Read<uint8_t>();
entry.color.tuple.g = input.Read<uint8_t>();
entry.color.tuple.b = input.Read<uint8_t>();
entry.color.tuple.a = input.Read<uint8_t>();
entry.color.tuple.a = 0xff;
palette[i] = entry;
}
}
};
static constexpr inline auto CalculateStride(int width, int bpp) { return ((width * bpp + 31) >> 5) << 2; }
static void MaskToAlpha(const uint8_t *mask, uint32_t *dst, int width, int height) {
auto stride = CalculateStride(width, 1); // stride in bytes for 1bpp DIB mask
IMAGE_DEBUG_PRINT("Width = %d, height = %d, stride = %d", width, height, stride);
for (auto y = 0; y < height; y++, mask += stride) {
for (auto x = 0; x < width; x++) {
// A black pixel in the mask means that the corresponding pixel in the image is copied
*dst = image_set_bgra_alpha(*dst, (~(mask[x >> 3] >> (7 - (x & 7))) & 0x1) * 0xff);
++dst;
}
}
}
static void FlipVertically(uint32_t *image, int width, int height) {
auto halfHeight = height >> 1;
IMAGE_DEBUG_PRINT("Width = %d, height = %d, halfHeight = %d", width, height, halfHeight);
for (auto y = 0; y < halfHeight; y++) {
auto topRow = image + y * width;
auto bottomRow = image + (height - y - 1) * width;
// Swap the top row with the bottom row
std::swap_ranges(topRow, topRow + width, bottomRow);
}
}
/// @brief This function simply writes a dummy header and directory entry to an icon file post which the PNG payload can be written
/// @param fileName The name of the icon file
/// @param payloadData A pointer to the PNG payload
/// @param payloadSize The size of the PNG payload in bytes
static auto WriteToFile(const char *fileName, const uint8_t *payloadData, uint32_t payloadSize) {
if (fileName && fileName[0] && payloadData && payloadSize) {
auto file = fopen(fileName, "wb");
if (file) {
// Write the main header
Header header(Type::Icon);
header.count = 1;
IMAGE_DEBUG_CHECK(header.IsValid());
header.WriteToFile(file);
// Write the directory entry
DirectoryEntry directoryEntry;
directoryEntry.bytesInRes = payloadSize;
directoryEntry.imageOffset = Header::GetStructSize() + DirectoryEntry::GetStructSize();
IMAGE_DEBUG_PRINT("Writing directory entry: bytesInRes = %u, imageOffset = %u", directoryEntry.bytesInRes, directoryEntry.imageOffset);
directoryEntry.WriteToFile(file);
IMAGE_DEBUG_PRINT("Writing payload: size = %u", payloadSize);
// Write the payload
if (fwrite(payloadData, sizeof(uint8_t), payloadSize, file) != payloadSize) {
fclose(file);
IMAGE_DEBUG_PRINT("Failed to write payload to %s", fileName);
return false;
}
fclose(file);
IMAGE_DEBUG_PRINT("Successfully saved to %s", fileName);
return true;
}
}
IMAGE_DEBUG_PRINT("Invalid parameters: fileName=%s, payloadData=%p, payloadSize=%u", fileName, payloadData, payloadSize);
return false;
}
public:
struct WriteToFileContext {
const char *fileName;
bool success;
};
static void WriteToFileCallback(void *context, void *data, int size) {
auto ctx = reinterpret_cast<WriteToFileContext *>(context);
ctx->success = WriteToFile(ctx->fileName, reinterpret_cast<const uint8_t *>(data), size);
}
void LoadFromMemory(const void *in_data, size_t in_dataSize, uint32_t **out_data, int *out_x, int *out_y) {
if (!in_data || !in_dataSize || !out_x || !out_y) {
IMAGE_DEBUG_PRINT("Invalid parameters: in_data=%p, in_dataSize=%llu, out_x=%p, out_y=%p", in_data, in_dataSize, out_x, out_y);
return;
}
Stream input(reinterpret_cast<const uint8_t *>(in_data), in_dataSize);
Header header(input);
if (!header.IsValid()) {
IMAGE_DEBUG_PRINT("Not an ICO/CUR file");
return;
}
IMAGE_DEBUG_PRINT("Type = %u, count = %u", unsigned(header.type), header.count);
std::vector<DirectoryEntry> directory(header.count);
// Read and probe the entire directory for the best image
for (size_t i = 0; i < header.count; i++) {
directory[i].ReadFromStream(input); // load the directory entry
IMAGE_DEBUG_PRINT("Width = %u, height = %u, colorCount = %u, bytesInRes = %u, imageOffset = %u", directory[i].width, directory[i].height,
directory[i].colorCount, directory[i].bytesInRes, directory[i].imageOffset);
}
size_t imageIndex = 0; // use the first image in the directory by default
bool foundBestImage = false;
// Find the best image (32bpp or 24bpp)
for (size_t i = 0; i < header.count; i++) {
// Simply pick one with the largest byte size
// Note that this can goof up if the file has mixed 32bpp RGB and PNG images. Not sure why anyone would create such a monstrosity
if (directory[i].width == 0 && directory[i].height == 0 && directory[i].colorCount == 0 &&
directory[i].bytesInRes >= directory[imageIndex].bytesInRes) {
IMAGE_DEBUG_PRINT("Attempt 1: entry %llu is better than %llu", i, imageIndex);
foundBestImage = true; // set the flag to true if we find the best image
imageIndex = i; // save the index and keep looking
}
}
if (!foundBestImage) {
// Try again, but this time just check the color count
for (size_t i = 0; i < header.count; i++) {
if (directory[i].colorCount == 0 && directory[i].bytesInRes >= directory[imageIndex].bytesInRes) {
IMAGE_DEBUG_PRINT("Attempt 2: entry %llu is better than %llu", i, imageIndex);
foundBestImage = true; // set the flag to true if we find the best image
imageIndex = i; // save the index and keep looking
}
}
if (!foundBestImage) {
// If we still did not find anything then we are probably dealing with a legacy file format. Simply pick one with the largest byte size
IMAGE_DEBUG_PRINT("Selecting image with largest bytesInRes");
for (size_t i = 0; i < header.count; i++) {
if (directory[i].bytesInRes >= directory[imageIndex].bytesInRes) {
IMAGE_DEBUG_PRINT("Attempt 3: entry %llu is better than %llu", i, imageIndex);
foundBestImage = true; // set the flag to true if we find the best image
imageIndex = i; // save the index and keep looking
}
}
}
}
IMAGE_DEBUG_PRINT(
"Selected index = %llu, width = %i, height = %i, colorCount = %u, planes_xHotspot = %u, bitCount_yHotspot = %u, bytesInRes = %u, imageOffset = %u",
imageIndex, directory[imageIndex].width, directory[imageIndex].height, directory[imageIndex].colorCount, directory[imageIndex].planes_xHotspot,
directory[imageIndex].bitCount_yHotspot, directory[imageIndex].bytesInRes, directory[imageIndex].imageOffset);
// Seek the location of the image data
input.Seek(directory[imageIndex].imageOffset);
// At this point the image can be a PNG or a Windows DIB
// We'll use stb_image if we detect that it's a PNG else we'll fallback to our Windows DIB code
auto imgSig = input.Read<uint32_t>();
if (imgSig == 0x474e5089u) {
// We have a PNG
input.Seek(directory[imageIndex].imageOffset); // seek to the start of the image data
IMAGE_DEBUG_PRINT("Loading PNG image");
int compOut;
*out_data = reinterpret_cast<uint32_t *>(
stbi_load_from_memory(reinterpret_cast<const stbi_uc *>(input.GetData()), input.GetSize() - input.GetPosition(), out_x, out_y, &compOut, 4));
IMAGE_DEBUG_CHECK(compOut == 4);
if (!*out_data)
IMAGE_DEBUG_PRINT("Failed to load PNG: %s", stbi_failure_reason());
} else if (imgSig == BmpInfoHeader::GetStructSize()) {
// We have a Windows DIB
input.Seek(directory[imageIndex].imageOffset); // seek to the start of the image data
IMAGE_DEBUG_PRINT("Loading Windows DIB image");
// Read the DIB header
BmpInfoHeader bmpInfoHeader;
bmpInfoHeader.ReadFromStream(input);
IMAGE_DEBUG_PRINT("Width = %u, height = %u, bitCount = %u, planes = %u, compression = %u, sizeImage = %u", bmpInfoHeader.width,
bmpInfoHeader.height, bmpInfoHeader.bitCount, bmpInfoHeader.planes, bmpInfoHeader.compression, bmpInfoHeader.sizeImage);
auto width = ::abs(bmpInfoHeader.width);
auto height = ::abs(bmpInfoHeader.height) >> 1; // height is always multiplied by 2 due to the mask
auto colors = 1llu << bmpInfoHeader.bitCount; // get the total number of colors in the image
auto stride = CalculateStride(width, bmpInfoHeader.bitCount); // calculate the stride
IMAGE_DEBUG_PRINT("Stride = %d, colors = %llu", stride, colors);
// Sanity check
if (width <= 0 || height <= 0 || !colors) {
IMAGE_DEBUG_PRINT("Invalid image properties");
return;
}
// Allocate memory for the image
*out_data = reinterpret_cast<uint32_t *>(malloc(width * height * sizeof(uint32_t)));
if (!(*out_data)) {
IMAGE_DEBUG_PRINT("Failed to allocate %lld bytes", width * height * sizeof(uint32_t));
return;
}
// Read the pixel data
switch (colors) {
case (1llu << 1): {
// This is a 1bpp (monochrome) image
Palette palette(input, colors);
IMAGE_DEBUG_PRINT("Read %llu colors from palette", colors);
// Read the image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
*dst = palette.GetColor(((src[x >> 3] >> (7 - (x & 7))) & 1)).color.value;
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
} break;
case (1llu << 4): {
// This is a 4bpp (16-color) image
Palette palette(input, colors);
IMAGE_DEBUG_PRINT("Read %llu colors from palette", colors);
// Read the image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
*dst = palette.GetColor((src[x >> 1] >> ((!(x & 1)) << 2)) & 0xF).color.value;
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
} break;
case (1llu << 8): {
// This is an 8bpp (256-color) image
Palette palette(input, colors);
IMAGE_DEBUG_PRINT("Read %llu colors from palette", colors);
// Read the image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
*dst = palette.GetColor(src[x]).color.value;
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
} break;
case (1llu << 15): {
// This is a 15bpp (555 format) image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto index = x << 1;
auto pixel = src[index] | (src[index + 1] << 8);
auto r = uint8_t(image_scale_5bits_to_8bits((pixel >> 10) & 0x1f));
auto g = uint8_t(image_scale_5bits_to_8bits((pixel >> 5) & 0x1f));
auto b = uint8_t(image_scale_5bits_to_8bits(pixel & 0x1f));
*dst = image_make_bgra(r, g, b);
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
} break;
case (1llu << 16): {
// This is a 16bpp (565 format) image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto index = x << 1;
auto pixel = src[index] | (src[index + 1] << 8);
auto r = uint8_t(image_scale_5bits_to_8bits((pixel >> 11) & 0x1f));
auto g = uint8_t(image_scale_6bits_to_8bits((pixel >> 5) & 0x3f));
auto b = uint8_t(image_scale_5bits_to_8bits(pixel & 0x1f));
*dst = image_make_bgra(r, g, b);
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
}
case (1llu << 24): {
// This is a 24bpp (888 format) image
auto src = input.GetData();
auto dst = *out_data;
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto index = x * 3;
auto r = src[index];
auto g = src[index + 1];
auto b = src[index + 2];
*dst = image_make_bgra(r, g, b);
++dst;
}
src += stride;
}
// Read and apply the mask
MaskToAlpha(src, *out_data, width, height);
} break;
case (1llu << 32): {
// This is a 32bpp (8888 format) image
// No stride and no mask is needed
auto src = input.GetData();
auto dst = *out_data;
size_t pixels = width * height;
for (size_t i = 0; i < pixels; i++) {
auto r = *src;
++src;
auto g = *src;
++src;
auto b = *src;
++src;
auto a = *src;
++src;
*dst = image_make_bgra(r, g, b, a);
++dst;
}
} break;
default:
// Unknown pixel format
IMAGE_DEBUG_PRINT("Unknown pixel format: %u", bmpInfoHeader.bitCount);
free(*out_data);
*out_data = nullptr;
return;
}
// Flip the image
FlipVertically(*out_data, width, height);
*out_x = width;
*out_y = height;
} else {
// Unknown image format
IMAGE_DEBUG_PRINT("Unknown image format: 0x%08x", imgSig);
}
}
};
uint32_t *curico_load_memory(const void *data, size_t dataSize, int *x, int *y, int *components) {
uint32_t *out_data = nullptr;
try {
std::unique_ptr<CurIcoImage> curico = std::make_unique<CurIcoImage>(); // use unique_ptr for memory management
curico->LoadFromMemory(data, dataSize, &out_data, x, y);
*components = 4; // always 32bpp BGRA
} catch (const std::exception &e) {
IMAGE_DEBUG_PRINT("Failed to load ICO/CUR: %s", e.what());
if (out_data) {
// Just in case this was allocated
free(out_data);
out_data = nullptr;
}
return nullptr;
}
return out_data;
}
uint32_t *curico_load_file(const char *filename, int *x, int *y, int *components) {
if (!filename || !filename[0] || !x || !y || !components) {
IMAGE_DEBUG_PRINT("Invalid parameters");
return nullptr;
}
auto pFile = fopen(filename, "rb");
if (!pFile) {
IMAGE_DEBUG_PRINT("Failed to open %s", filename);
return nullptr;
}
if (fseek(pFile, 0, SEEK_END)) {
IMAGE_DEBUG_PRINT("Failed to seek %s", filename);
fclose(pFile);
return nullptr;
}
auto len = ftell(pFile);
if (len < 0) {
IMAGE_DEBUG_PRINT("Failed to get length of %s", filename);
fclose(pFile);
return nullptr;
}
std::vector<uint8_t> buffer(len);
rewind(pFile);
if (fread(&buffer[0], sizeof(uint8_t), len, pFile) != len || ferror(pFile)) {
IMAGE_DEBUG_PRINT("Failed to read %s", filename);
fclose(pFile);
return nullptr;
}
fclose(pFile);
return curico_load_memory(&buffer[0], len, x, y, components);
}
bool curico_save_file(const char *filename, int x, int y, int components, const void *data) {
if (!filename || !filename[0] || !data || x < 1 || y < 1 || components != sizeof(uint32_t)) {
IMAGE_DEBUG_PRINT("Invalid parameters");
return false;
}
// Just write a PNG using the stb_image_writer callback
CurIcoImage::WriteToFileContext context = {filename, false};
stbi_write_png_compression_level = 100;
auto stbiResult = stbi_write_png_to_func(&CurIcoImage::WriteToFileCallback, &context, x, y, components, data, 0) != 0;
return stbiResult && context.success;
}

View file

@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------------------------------
// Windows Cursor & Icon Loader for QB64-PE by a740g
//
// Bibliography:
// https://en.wikipedia.org/wiki/ICO_(file_format)
// https://learn.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10)
// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
// https://devblogs.microsoft.com/oldnewthing/20101019-00/?p=12503
// https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473
// https://books.google.co.in/books?id=LpkFEO2FG8sC&pg=PA318&redir_esc=y#v=onepage&q&f=false
// https://books.google.co.in/books?id=qR6ngUchllsC&pg=PA349&redir_esc=y#v=onepage&q&f=false
// https://www.informit.com/articles/article.aspx?p=1186882
//-----------------------------------------------------------------------------------------------------
#pragma once
#include <cstdint>
#include <cstdlib>
uint32_t *curico_load_memory(const void *data, size_t dataSize, int *x, int *y, int *components);
uint32_t *curico_load_file(const char *filename, int *x, int *y, int *components);
bool curico_save_file(const char *filename, int x, int y, int components, const void *data);

View file

@ -6,11 +6,16 @@
// https://github.com/mackron/dr_pcx
//-----------------------------------------------------------------------------------------------------
// Uncomment this to to print debug messages to stderr
// #define IMAGE_DEBUG 1
#include "sg_pcx.h"
#include "image.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class PCXImage {
@ -43,6 +48,8 @@ class PCXImage {
void Seek(size_t position) {
if (position <= size)
cursor = position;
else
throw std::runtime_error("Failed to seek to position " + std::to_string(position) + " of " + std::to_string(size));
}
template <typename T> T Read() {
@ -183,15 +190,13 @@ class PCXImage {
Color(uint32_t value) { color.value = value; }
Color(uint8_t b, uint8_t g, uint8_t r, uint8_t a) { SetFromComponents(b, g, r, a); }
Color(uint8_t b, uint8_t g, uint8_t r, uint8_t a = 0xFFu) { SetFromComponents(b, g, r, a); }
Color(uint8_t b, uint8_t g, uint8_t r) { SetFromComponents(b, g, r); }
void SetFromComponents(uint8_t b, uint8_t g, uint8_t r, uint8_t a = 255) {
this->color.tuple.b = b;
this->color.tuple.g = g;
this->color.tuple.r = r;
this->color.tuple.a = a;
void SetFromComponents(uint8_t b, uint8_t g, uint8_t r, uint8_t a = 0xFFu) {
color.tuple.b = b;
color.tuple.g = g;
color.tuple.r = r;
color.tuple.a = a;
}
};
@ -376,11 +381,11 @@ class PCXImage {
};
public:
bool LoadFromMemory(const void *in_data, size_t in_dataSize, uint32_t **out_data, int *out_x, int *out_y) {
void LoadFromMemory(const void *in_data, size_t in_dataSize, uint32_t **out_data, int *out_x, int *out_y) {
if (!in_data || !in_dataSize || !out_x || !out_y) {
IMAGE_DEBUG_PRINT("Invalid parameters: in_data=%p, in_dataSize=%llu, out_x=%p, out_y=%p", in_data, in_dataSize, out_x, out_y);
return false;
return;
}
// Prepare the memory input stream
@ -392,20 +397,20 @@ class PCXImage {
if (header.id != Id::ZSoftPCX) {
IMAGE_DEBUG_PRINT("Not a PCX file");
return false;
return;
}
if (header.version != Version::Version3_0 && header.version != Version::Version2_8_Palette && header.version != Version::Version2_8_DefaultPalette &&
header.version != Version::Version2_5) {
IMAGE_DEBUG_PRINT("Unsupported PCX version: %d", int(header.version));
return false;
return;
}
if (header.bitsPerPixel != 1 && header.bitsPerPixel != 2 && header.bitsPerPixel != 4 && header.bitsPerPixel != 8) {
IMAGE_DEBUG_PRINT("Unsupported PCX bits per pixel: %d", int(header.bitsPerPixel));
return false;
return;
}
auto width = header.xMax - header.xMin + 1;
@ -413,7 +418,7 @@ class PCXImage {
if (width < 0 || height < 0 || width > 0xffff || height > 0xffff) {
IMAGE_DEBUG_PRINT("Invalid image dimensions: (%d, %d) - (%d, %d)", header.xMin, header.yMin, header.xMax, header.yMax);
return false;
return;
}
// Pixels per line, including PCX's even-number-of-pixels buffer
@ -425,7 +430,7 @@ class PCXImage {
if (bitsPerPixel != 1 && bitsPerPixel != 2 && bitsPerPixel != 4 && bitsPerPixel != 8 && bitsPerPixel != 24) {
IMAGE_DEBUG_PRINT("Unsupported PCX bit depth: %d", bitsPerPixel);
return false;
return;
}
// Load the palette
@ -475,7 +480,7 @@ class PCXImage {
if (input.Read<uint8_t>() != PaletteMarker) {
IMAGE_DEBUG_PRINT("PCX palette marker not present in file");
return false;
return;
}
palette.LoadFromStream(input, 256);
@ -492,7 +497,7 @@ class PCXImage {
if (!(*out_data)) {
IMAGE_DEBUG_PRINT("Failed to allocate %lld bytes", width * height * sizeof(uint32_t));
return false;
return;
}
memset(*out_data, 0xff, width * height * sizeof(uint32_t));
@ -543,8 +548,6 @@ class PCXImage {
*out_x = width;
*out_y = height;
return true;
}
};

View file

@ -0,0 +1,15 @@
//-----------------------------------------------------------------------------------------------------
// PCX Loader for QB64-PE by a740g
//
// Uses code and ideas from:
// https://github.com/EzArIk/PcxFileType
// https://github.com/mackron/dr_pcx
//-----------------------------------------------------------------------------------------------------
#pragma once
#include <cstdint>
#include <cstdlib>
uint32_t *pcx_load_memory(const void *data, size_t dataSize, int *x, int *y, int *components);
uint32_t *pcx_load_file(const char *filename, int *x, int *y, int *components);

View file

@ -0,0 +1,4 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 KiB

View file

@ -0,0 +1,94 @@
DEFLNG A-Z
OPTION _EXPLICIT
$CONSOLE:ONLY
CHDIR _STARTDIR$
CONST TEST_FILE_FORMAT = "bmp"
CONST TOLERANCE_LIMIT = 0
ON ERROR GOTO error_handler
RESTORE file_list
DIM fileName AS STRING: READ fileName
DO
DoImageFile fileName
READ fileName
LOOP WHILE LEN(fileName)
SYSTEM
error_handler:
PRINT "Error: "; _ERRORMESSAGE$; " at line"; _ERRORLINE;
DIM incErrFile AS STRING: incErrFile = _INCLERRORFILE$
DIM incErrLine AS LONG: incErrLine = _INCLERRORLINE
IF LEN(incErrFile) THEN
PRINT "( "; incErrFile + " at line"; incErrLine; ")"
ELSE
PRINT
END IF
SYSTEM
file_list:
DATA "1bpp.ico"
DATA "24bpp.ico"
DATA "4bpp.cur"
DATA "4bpp.ico"
DATA "8bpp.ico"
DATA "8bpp.cur"
DATA "bard.ico"
DATA "broom.ico"
DATA "cat.ico"
DATA "computer.ico"
DATA "dumb1.ico"
DATA "dumb2.ico"
DATA "pencil.ico"
DATA "please_wait.ico"
DATA "question.ico"
DATA "really_huge_duck.ico"
DATA "sample3.ico"
DATA "sample4.ico"
DATA "soft_drink.ico"
DATA "taco.ico"
DATA "terra_globe.ico"
DATA "thumbs_up.ico"
DATA "volme_dialog.ico"
DATA "word-icon.ico"
DATA "x.ico"
DATA ""
SUB PrintImageDetails (handle AS LONG, testFileName AS STRING)
DIM fullTestFileName AS STRING: fullTestFileName = testFileName + "." + TEST_FILE_FORMAT
PRINT "Testing against "; fullTestFileName; " ("; _WIDTH(handle); "x"; _HEIGHT(handle); ")."
'_SAVEIMAGE fullTestFileName, handle
AssertImage2 handle, fullTestFileName, TOLERANCE_LIMIT
PRINT
END SUB
SUB DoImageFile (testFileName AS STRING)
DIM fileName AS STRING: fileName = testFileName
PRINT "Loading image from storage "; fileName; " ... ";
DIM h AS LONG: h = _LOADIMAGE(fileName, 32)
IF h < -1 THEN
PRINT "done."
PrintImageDetails h, testFileName
_FREEIMAGE h
ELSE
PRINT "failed!"
END IF
END SUB
'$INCLUDE:'../utilities/imageassert.bm'
'$INCLUDE:'../utilities/base64.bm'

View file

@ -0,0 +1,100 @@
Loading image from storage 1bpp.ico ... done.
Testing against 1bpp.ico.bmp ( 128 x 128 ).
Success, images are identical!
Loading image from storage 24bpp.ico ... done.
Testing against 24bpp.ico.bmp ( 128 x 128 ).
Success, images are identical!
Loading image from storage 4bpp.cur ... done.
Testing against 4bpp.cur.bmp ( 16 x 16 ).
Success, images are identical!
Loading image from storage 4bpp.ico ... done.
Testing against 4bpp.ico.bmp ( 128 x 128 ).
Success, images are identical!
Loading image from storage 8bpp.ico ... done.
Testing against 8bpp.ico.bmp ( 128 x 128 ).
Success, images are identical!
Loading image from storage 8bpp.cur ... done.
Testing against 8bpp.cur.bmp ( 32 x 32 ).
Success, images are identical!
Loading image from storage bard.ico ... done.
Testing against bard.ico.bmp ( 1360 x 1083 ).
Success, images are identical!
Loading image from storage broom.ico ... done.
Testing against broom.ico.bmp ( 600 x 600 ).
Success, images are identical!
Loading image from storage cat.ico ... done.
Testing against cat.ico.bmp ( 150 x 150 ).
Success, images are identical!
Loading image from storage computer.ico ... done.
Testing against computer.ico.bmp ( 474 x 474 ).
Success, images are identical!
Loading image from storage dumb1.ico ... done.
Testing against dumb1.ico.bmp ( 540 x 500 ).
Success, images are identical!
Loading image from storage dumb2.ico ... done.
Testing against dumb2.ico.bmp ( 540 x 500 ).
Success, images are identical!
Loading image from storage pencil.ico ... done.
Testing against pencil.ico.bmp ( 256 x 256 ).
Success, images are identical!
Loading image from storage please_wait.ico ... done.
Testing against please_wait.ico.bmp ( 404 x 325 ).
Success, images are identical!
Loading image from storage question.ico ... done.
Testing against question.ico.bmp ( 512 x 512 ).
Success, images are identical!
Loading image from storage really_huge_duck.ico ... done.
Testing against really_huge_duck.ico.bmp ( 4293 x 4293 ).
Success, images are identical!
Loading image from storage sample3.ico ... done.
Testing against sample3.ico.bmp ( 256 x 171 ).
Success, images are identical!
Loading image from storage sample4.ico ... done.
Testing against sample4.ico.bmp ( 256 x 192 ).
Success, images are identical!
Loading image from storage soft_drink.ico ... done.
Testing against soft_drink.ico.bmp ( 256 x 256 ).
Success, images are identical!
Loading image from storage taco.ico ... done.
Testing against taco.ico.bmp ( 966 x 764 ).
Success, images are identical!
Loading image from storage terra_globe.ico ... done.
Testing against terra_globe.ico.bmp ( 772 x 769 ).
Success, images are identical!
Loading image from storage thumbs_up.ico ... done.
Testing against thumbs_up.ico.bmp ( 463 x 294 ).
Success, images are identical!
Loading image from storage volme_dialog.ico ... done.
Testing against volme_dialog.ico.bmp ( 32 x 32 ).
Success, images are identical!
Loading image from storage word-icon.ico ... done.
Testing against word-icon.ico.bmp ( 512 x 512 ).
Success, images are identical!
Loading image from storage x.ico ... done.
Testing against x.ico.bmp ( 256 x 256 ).
Success, images are identical!

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View file

@ -118,7 +118,7 @@ END SUB
FUNCTION GetColorDelta~& (color1 AS _UNSIGNED LONG, color2 AS _UNSIGNED LONG)
GetColorDelta = ABS(_RED32(color1) - _RED32(color2)) + ABS(_GREEN32(color1) - _GREEN32(color2)) + ABS(_BLUE32(color1) - _BLUE32(color2)) + ABS(_ALPHA32(color1) - _ALPHA32(color2))
GetColorDelta = ABS(_RED32(color1) - _RED32(color2)) + ABS(_GREEN32(color1) - _GREEN32(color2)) + ABS(_BLUE32(color1) - _BLUE32(color2))
END FUNCTION
@ -178,7 +178,7 @@ SUB AssertImage2 (originalActualImage AS LONG, expectedFileName AS STRING, toler
actualPixel = _MEMGET(actual, actual.OFFSET + pixelOffset, _UNSIGNED LONG)
expectedPixel = _MEMGET(expected, expected.OFFSET + pixelOffset, _UNSIGNED LONG)
IF actualPixel <> expectedPixel _ANDALSO GetColorDelta(actualPixel, expectedPixel) > toleranceLimit THEN
IF actualPixel <> expectedPixel _ANDALSO _ALPHA32(actualPixel) <> 0 _ANDALSO _ALPHA32(expectedPixel) <> 0 _ANDALSO GetColorDelta(actualPixel, expectedPixel) > toleranceLimit THEN
PRINT "Failure! Image pixels at ("; x&; ","; y&; ") differ, actual: 0x"; HEX$(actualPixel); ", expected: 0x"; HEX$(expectedPixel)
GOTO freeImages
END IF