diff options
Diffstat (limited to 'convert.c')
-rw-r--r-- | convert.c | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/convert.c b/convert.c new file mode 100644 index 0000000..e2191ce --- /dev/null +++ b/convert.c @@ -0,0 +1,344 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <err.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +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; +} |