/*
* Copyright (C) 2020 Mikhail Burakov. This file is part of Pui.
*
* Pui 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 3 of the License, or
* (at your option) any later version.
*
* Pui is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Pui. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
enum {
kNoSection = 0,
kSectionElements,
kSectionDefaultPalette,
kSectionActivePalette
};
struct PuiElement {
uint8_t x, y, w, h;
};
struct MappedFile {
void* data;
size_t size;
};
struct BitmapHeader {
uint16_t magic;
uint32_t size;
uint16_t reserved[2];
uint32_t offset;
} __attribute__((packed));
struct DibHeader {
uint32_t size;
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bpp;
} __attribute__((packed));
struct Bitmap {
struct MappedFile file;
struct BitmapHeader* bitmap_header;
struct DibHeader* dib_header;
uint8_t* ptr;
int index;
};
static void MapFile(const char* file, struct MappedFile* result) {
int fd = open(file, O_RDONLY);
if (fd == -1) err(1, "Failed to open file");
struct stat buf;
if (fstat(fd, &buf)) err(1, "Failed to stat file");
void* data = mmap(NULL, (size_t)buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (data == MAP_FAILED) err(1, "Failed to map file");
result->data = data;
result->size = (size_t)buf.st_size;
}
static void UnmapFile(struct MappedFile* result) {
munmap(result->data, result->size);
}
static int ReadBitmap(struct Bitmap* bitmap) {
uint8_t* buffer = bitmap->file.data;
if (bitmap->ptr < buffer + bitmap->bitmap_header->offset) return -1;
int result = bitmap->ptr[bitmap->index >> 1] >> ((~bitmap->index & 1) << 2);
if (++bitmap->index == bitmap->dib_header->width) {
size_t stride = ((size_t)(bitmap->dib_header->width >> 1) + 3) & ~3u;
bitmap->ptr -= stride;
bitmap->index = 0;
}
return result & 0x7;
}
static void OpenBitmap(const char* fname, struct Bitmap* bitmap) {
MapFile(fname, &bitmap->file);
uint8_t* data = bitmap->file.data;
bitmap->bitmap_header = (struct BitmapHeader*)data;
bitmap->dib_header = (struct DibHeader*)(data + sizeof(struct BitmapHeader));
if (bitmap->bitmap_header->magic != 0x4d42) err(1, "Invalid bitmap magic");
if (bitmap->dib_header->width < 0 || bitmap->dib_header->height < 0 ||
bitmap->dib_header->width > UINT16_MAX ||
bitmap->dib_header->height > UINT16_MAX ||
bitmap->dib_header->planes != 1 || bitmap->dib_header->bpp != 4)
err(1, "Unsupported bitmap format");
size_t stride = ((size_t)(bitmap->dib_header->width >> 1) + 3) & ~3u;
bitmap->ptr = data + bitmap->bitmap_header->offset +
stride * (size_t)(bitmap->dib_header->height - 1);
}
static void Write(int item, FILE* out) {
static int current = -1;
if (current == -1) {
if (item == -1) return;
current = (item & 0xf) << 4;
} else {
if (item == -1) item = 0;
current |= item & 0xf;
fputc(current, out);
current = -1;
}
}
static void WriteCounter(int counter, FILE* out) {
int pos = 0;
while (counter >> (++pos * 3))
;
while (pos-- > 0) Write(0x8 | counter >> (pos * 3), out);
}
static void WriteSequence(int prev, int counter, FILE* out) {
switch (counter) {
case 1:
Write(prev, out);
break;
case 2:
Write(prev, out);
Write(prev, out);
break;
default:
WriteCounter(counter - 2, out);
Write(prev, out);
break;
}
}
static void SelectScale(int32_t size, uint8_t* value, uint8_t* scale) {
if (size < 0) err(1, "Size must be non-negative");
for (int shift = 0, mask = 0;; shift++, mask = mask << 1 | 1) {
if (size & mask) err(1, "Size is not aligned");
if (size < 0x100 << shift) {
*value = (uint8_t)(size >> shift);
*scale = (uint8_t)shift;
return;
}
}
}
static int IsMeaningless(int ch) { return !isgraph(ch); }
static int IsMeaningful(int ch) { return isprint(ch); }
static void SkipGroup(struct MappedFile* file, int (*pred)(int ch)) {
char* ptr = file->data;
size_t size = file->size;
for (; size && pred(*ptr); ptr++, size--)
;
file->data = ptr;
file->size = size;
}
static int ChangeSection(struct MappedFile* file) {
char* begin = file->data;
SkipGroup(file, IsMeaningful);
size_t size = (size_t)((char*)file->data - begin);
static const char kElements[] = {'[', 'E', 'l', 'e', 'm',
'e', 'n', 't', 's', ']'};
static const char kDefaultPalette[] = {'[', 'D', 'e', 'f', 'a', 'u',
'l', 't', 'P', 'a', 'l', 'e',
't', 't', 'e', ']'};
static const char kActivePalette[] = {'[', 'A', 'c', 't', 'i', 'v', 'e', 'P',
'a', 'l', 'e', 't', 't', 'e', ']'};
switch (size) {
case sizeof(kElements):
if (!memcmp(begin, kElements, sizeof(kElements))) return kSectionElements;
break;
case sizeof(kDefaultPalette):
if (!memcmp(begin, kDefaultPalette, sizeof(kDefaultPalette)))
return kSectionDefaultPalette;
break;
case sizeof(kActivePalette):
if (!memcmp(begin, kActivePalette, sizeof(kActivePalette)))
return kSectionActivePalette;
break;
default:
break;
}
return kNoSection;
}
static int GetIndex(char* ptr, size_t size, const char* prefix,
size_t prefix_length) {
if (size < prefix_length + 3 || memcmp(ptr, prefix, prefix_length) ||
!isdigit(ptr[prefix_length]) || !isdigit(ptr[prefix_length + 1]) ||
ptr[prefix_length + 2] != '=')
return -1;
return atoi(ptr + prefix_length);
}
static int ParseElement(struct MappedFile* file, uint8_t scale[2],
struct PuiElement* elements) {
char* ptr = file->data;
SkipGroup(file, IsMeaningful);
size_t size = (size_t)((char*)file->data - ptr);
static const char kElement[] = {'E', 'l', 'e', 'm', 'e', 'n', 't'};
int index = GetIndex(ptr, size, kElement, sizeof(kElement));
if (index == -1 || index > 14) return -1;
ptr += sizeof(kElement) + 3;
size -= sizeof(kElement) + 3;
int values[] = {0, 0, 0, 0};
int *value = values, *end = values + 4;
for (; size; ptr++, size--) {
if ('0' <= *ptr && *ptr <= '9') {
*value = *value * 10 + *ptr - '0';
} else if (*ptr == ',') {
if (++value == end) break;
} else {
break;
}
}
elements[index].x = (uint8_t)(values[0] >> scale[0]);
elements[index].y = (uint8_t)(values[1] >> scale[1]);
elements[index].w = (uint8_t)(values[2] >> scale[0]);
elements[index].h = (uint8_t)(values[3] >> scale[1]);
return index;
}
static int ParsePalette(struct MappedFile* file, uint32_t* palette) {
char* ptr = file->data;
SkipGroup(file, IsMeaningful);
size_t size = (size_t)((char*)file->data - ptr);
static const char kColor[] = {'C', 'o', 'l', 'o', 'r'};
int index = GetIndex(ptr, size, kColor, sizeof(kColor));
if (index == -1 || index > 7) return -1;
ptr += sizeof(kColor) + 3;
size -= sizeof(kColor) + 3;
uint32_t value = 0;
for (; size; ptr++, size--) {
if ('0' <= *ptr && *ptr <= '9') {
value = value << 4 | (unsigned)(*ptr - '0');
} else if ('A' <= *ptr && *ptr <= 'F') {
value = value << 4 | (unsigned)(*ptr - 'A' + 10);
} else if ('a' <= *ptr && *ptr <= 'f') {
value = value << 4 | (unsigned)(*ptr - 'a' + 10);
} else {
break;
}
}
palette[index] = (value >> 24 & 0xff) | (value >> 8 & 0xff00) |
(value << 8 & 0xff0000) | (value << 24 & 0xff000000);
return index;
}
static void CompressDefinition(const struct MappedFile* definition,
uint8_t scale[2], FILE* out) {
struct PuiElement elements[0xf];
memset(elements, 0, sizeof(elements));
uint32_t palettes[2][8];
memset(palettes, 0, sizeof(palettes));
int section = kNoSection;
int max_element = 0, max_color = 0;
for (struct MappedFile def = *definition;;) {
SkipGroup(&def, IsMeaningless);
if (!def.size) break;
switch (*(char*)def.data) {
case '#':
SkipGroup(&def, IsMeaningful);
continue;
case '[':
section = ChangeSection(&def);
continue;
default:
break;
}
switch (section) {
case kSectionElements: {
int index = ParseElement(&def, scale, elements);
if (index > max_element) max_element = index;
break;
}
case kSectionDefaultPalette: {
int index = ParsePalette(&def, palettes[0]);
if (index > max_color) max_color = index;
break;
}
case kSectionActivePalette: {
int index = ParsePalette(&def, palettes[1]);
if (index > max_color) max_color = index;
break;
}
default:
SkipGroup(&def, IsMeaningful);
break;
}
}
int count_elements = max_element + 1;
int count_colors = max_color + 1;
uint8_t counter = (count_elements << 4 & 0xf0) | (count_colors & 0xf);
fwrite(&counter, 1, 1, out);
fwrite(elements, (size_t)count_elements * sizeof(struct PuiElement), 1, out);
fwrite(palettes[0], (size_t)count_colors * sizeof(uint32_t), 1, out);
fwrite(palettes[1], (size_t)count_colors * sizeof(uint32_t), 1, out);
}
int main(int argc, char** argv) {
if (argc < 3) err(1, "Usage: %s [source.bmp] [definition.ini]", argv[0]);
struct Bitmap bitmap;
memset(&bitmap, 0, sizeof(bitmap));
OpenBitmap(argv[1], &bitmap);
uint8_t whs[3], scale[2];
SelectScale(bitmap.dib_header->width, &whs[0], &scale[0]);
SelectScale(bitmap.dib_header->height, &whs[1], &scale[1]);
struct MappedFile definition;
MapFile(argv[2], &definition);
whs[2] = (uint8_t)(scale[0] << 4 | scale[1]);
fwrite(whs, sizeof(whs), 1, stdout);
CompressDefinition(&definition, scale, stdout);
UnmapFile(&definition);
for (int counter = 0, prev = -1;;) {
int current = ReadBitmap(&bitmap);
if (current == prev || prev == -1) {
counter++;
} else {
WriteSequence(prev, counter, stdout);
if (current == -1) break;
counter = 1;
}
prev = current;
}
UnmapFile(&bitmap.file);
Write(-1, stdout);
return 0;
}