From 43821b0cffc5aa419c0218992f06f8962ae54a13 Mon Sep 17 00:00:00 2001 From: Frederico Linhares Date: Wed, 8 May 2024 17:56:29 -0300 Subject: refa Rename graphical engine to BluCat --- src/blucat/qoi.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/blucat/qoi.cpp (limited to 'src/blucat/qoi.cpp') diff --git a/src/blucat/qoi.cpp b/src/blucat/qoi.cpp new file mode 100644 index 0000000..663e4e9 --- /dev/null +++ b/src/blucat/qoi.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "qoi.hpp" + +#include +#include + +#include "../binary_reader.hpp" + +namespace +{ + +const char MAGIC[]{"qoif"}; +const int HEADER_SIZE{14}; +const uint8_t PADDING[8]{0,0,0,0,0,0,0,1}; +const unsigned int PIXELS_MAX{400000000}; + +const int OP_INDEX{0b00000000}; +const int OP_DIFF {0b01000000}; +const int OP_LUMA {0b10000000}; +const int OP_RUN {0b11000000}; +const int OP_RGB {0b11111110}; +const int OP_RGBA {0b11111111}; + +const int MASK_2_BITS{0b11000000}; + +struct RGBA +{ + uint8_t red, green, blue, alpha; + + RGBA(); + RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); +}; + +RGBA::RGBA(): + red{0}, + green{0}, + blue{0}, + alpha{0} +{ +} + +RGBA::RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha): + red{red}, + green{green}, + blue{blue}, + alpha{alpha} +{ +} + +struct Pixel +{ + union + { + RGBA colors; + uint32_t value; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); +}; + +Pixel::Pixel(): + colors{} +{ +} + +Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha): + colors(red, green, blue, alpha) +{ +} + +inline int +color_hash(const RGBA &colors) +{ + return colors.red*3 + colors.green*5 + colors.blue*7 + colors.alpha*11; +} + +} + +namespace BluCat::QOI +{ + +Image::Image(const char *file_path, uint8_t channels): + channels{channels} +{ + if(this->channels != 0 && this->channels != 3 && this->channels != 4) + { + throw std::invalid_argument{"invalid number of channels"}; + } + + BinaryReader input{file_path}; + if(input.size() < HEADER_SIZE + (int)sizeof(PADDING)) + { + throw std::runtime_error{"invalid QOI file"}; + } + + input.read_chars(this->header.magic, 4); + this->header.width = input.read_ui32(); + this->header.height = input.read_ui32(); + this->header.channels = input.read_ui8(); + this->header.colorspace = input.read_ui8(); + + if(this->header.width == 0 || this->header.height == 0 || + this->header.channels < 3 || this->header.channels > 4 || + this->header.colorspace > 1 || + this->header.height >= PIXELS_MAX / this->header.width) + { + throw std::runtime_error{"QOI file have an invalid header"}; + } + + if(this->channels == 0) this->channels = this->header.channels; + + uint32_t num_pixels{this->header.width * this->header.height}; + this->pixels_len = num_pixels * this->channels; + this->pixels = new uint8_t[this->pixels_len]; + + std::array index; + Pixel pixel(0, 0, 0, 255); + int chunks_len = input.size() - (int)sizeof(PADDING); + + /* + This algorithm is based on the original implementation that is in GitHub: + https://github.com/phoboslab/qoi + + For information about the QOI image format, see: https://qoiformat.org/ + */ + int pixel_p{0}; + int run{0}; + for(uint32_t decoded_pixel{0}; decoded_pixel < num_pixels; decoded_pixel++) + { + if(run > 0) + { + run--; + } + else if(input.pointer() < chunks_len) + { + int b1 = input.read_ui8(); + + if (b1 == OP_RGB) + { + pixel.colors.red = input.read_ui8(); + pixel.colors.green = input.read_ui8(); + pixel.colors.blue = input.read_ui8(); + } + else if (b1 == OP_RGBA) + { + pixel.colors.red = input.read_ui8(); + pixel.colors.green = input.read_ui8(); + pixel.colors.blue = input.read_ui8(); + pixel.colors.alpha = input.read_ui8(); + } + else if ((b1 & MASK_2_BITS) == OP_INDEX) + { + pixel = index[b1]; + } + else if ((b1 & MASK_2_BITS) == OP_DIFF) + { + pixel.colors.red += ((b1 >> 4) & 0x03) - 2; + pixel.colors.green += ((b1 >> 2) & 0x03) - 2; + pixel.colors.blue += (b1 & 0x03) - 2; + } + else if ((b1 & MASK_2_BITS) == OP_LUMA) + { + int b2 = input.read_ui8(); + int vg = (b1 & 0x3f) - 32; + pixel.colors.red += vg - 8 + ((b2 >> 4) & 0x0f); + pixel.colors.green += vg; + pixel.colors.blue += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & MASK_2_BITS) == OP_RUN) + { + run = (b1 & 0x3f); + } + + index[color_hash(pixel.colors) % 64] = pixel; + } + + this->pixels[pixel_p++] = pixel.colors.red; + this->pixels[pixel_p++] = pixel.colors.green; + this->pixels[pixel_p++] = pixel.colors.blue; + if(this->channels == 4) this->pixels[pixel_p++] = pixel.colors.alpha; + } +} + +Image::~Image() +{ + delete[] this->pixels; +} + +} -- cgit v1.2.3