/* * 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; }