/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2024 Melin Software HB This program 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. This program 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 this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ #include "stdafx.h" #include "image.h" //#include "png/png.h" #include "libpng16/png.h" #include #include #include #include #include "meosexception.h" FILE _iob[] = { *stdin, *stdout, *stderr }; extern "C" FILE * __cdecl __iob_func(void) { return _iob; } namespace { struct PngData { vector memory; size_t ptr; PngData() : ptr(0) {} size_t read(uint8_t *dst, size_t count); }; void readDataFromInputStream(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { png_voidp io_ptr = png_get_io_ptr(png_ptr); if (io_ptr == NULL) return; // add custom error handling here PngData& inputStream = *(PngData*)io_ptr; const size_t bytesRead = inputStream.read((byte*)outBytes, (size_t)byteCountToRead); if ((png_size_t)bytesRead != byteCountToRead) return; // add custom error handling here } } size_t PngData::read(uint8_t *dst, size_t count) { count = min(size_t(memory.size() - ptr), count); memcpy(dst, &memory[ptr], count); ptr += count; return count; } // Creates a stream object initialized with the data from an executable resource. vector Image::loadResourceToMemory(LPCTSTR lpName, LPCTSTR lpType) { vector result; // find the resource HRSRC hrsrc = FindResource(NULL, lpName, lpType); if (hrsrc == NULL) return result; // load the resource DWORD dwResourceSize = SizeofResource(NULL, hrsrc); HGLOBAL hglbImage = LoadResource(NULL, hrsrc); if (hglbImage == NULL) return result; // lock the resource, getting a pointer to its data LPVOID pvSourceResourceData = LockResource(hglbImage); result.resize(dwResourceSize); memcpy(&result[0], pvSourceResourceData, dwResourceSize); return result; } HBITMAP Image::read_png(vector &&inData, int &width, int &height, ImageMethod method) { PngData inputStream; inputStream.memory = std::move(inData); png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) return nullptr; png_infop info = png_create_info_struct(png); if (!info) return nullptr; png_set_read_fn(png, &inputStream, readDataFromInputStream); png_read_info(png, info); width = png_get_image_width(png, info); height = png_get_image_height(png, info); int color_type = png_get_color_type(png, info); int bit_depth = png_get_bit_depth(png, info); // Read any color_type into 8bit depth, RGBA format. // See http://www.libpng.org/pub/png/libpng-manual.txt if (bit_depth == 16) png_set_strip_16(png); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); // These color_type don't have an alpha channel then fill it with 0xff. if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); png_read_update_info(png, info); int rowb = png_get_rowbytes(png, info); vector> data(height, vector(rowb, 0)); vector row_pointers_vec(height); for (int y = 0; y < height; y++) { row_pointers_vec[y] = &data[y][0]; } png_bytepp row_pointers = &row_pointers_vec[0]; png_read_image(png, row_pointers); // initialize return value HBITMAP hbmp = NULL; // prepare structure giving bitmap information (negative height indicates a top-down DIB) BITMAPINFO bminfo; ZeroMemory(&bminfo, sizeof(bminfo)); bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bminfo.bmiHeader.biWidth = width; bminfo.bmiHeader.biHeight = ((LONG)-height); bminfo.bmiHeader.biPlanes = 1; bminfo.bmiHeader.biBitCount = 32; bminfo.bmiHeader.biCompression = BI_RGB; // create a DIB section that can hold the image void * pvImageBits = NULL; HDC hdcScreen = GetDC(NULL); hbmp = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0); ReleaseDC(NULL, hdcScreen); // extract the image into the HBITMAP const size_t cbStride = width * 4; const size_t cbImage = cbStride * height; byte *dst = static_cast(pvImageBits); for (int y = 0; y < height; y++) { byte *row = dst + cbStride * y; byte *src = row_pointers_vec[y]; if (method == ImageMethod::MonoAlpha) { for (size_t x = 0; x < cbStride; x += 4) { row[x + 2] = 0;// src[x + 0]; // Red row[x + 1] = 0;// src[x + 1]; // Green row[x + 0] = 16;// src[x + 2]; // Blue row[x + 3] = 255 - src[x + 0];// ((x/100)%8)*31+1;// 255 - src[x + 0]; // Alpha if (row[x + 3] == 0) { row[x + 1] = 0; row[x + 2] = 0; row[x + 0] = 0; } } } else if (method == ImageMethod::Default) { for (size_t x = 0; x < cbStride; x += 4) { row[x + 2] = src[x + 0]; // Red row[x + 1] = src[x + 1]; // Green row[x + 0] = src[x + 2]; // Blue row[x + 3] = src[x + 3]; } } else if (method == ImageMethod::WhiteTransparent) { for (size_t x = 0; x < cbStride; x += 4) { row[x + 2] = src[x + 0]; // Red row[x + 1] = src[x + 1]; // Green row[x + 0] = src[x + 2]; // Blue row[x + 3] = src[x + 3]; if (src[x + 0] == 0xFF && src[x + 1] == 0xFF && src[x + 2] == 0xFF) { row[x + 3] = 0; row[x + 2] = 0; row[x + 1] = 0; row[x + 0] = 0; } } } } return hbmp; } uint64_t Image::computeHash(const vector& data) { uint64_t h = data.size(); size_t siz4 = data.size() / 4; const uint32_t* ptr = (const uint32_t *)data.data(); size_t lim = siz4 * 4; for (size_t e = siz4 * 4; e < data.size(); e++) h = h * 256 + data[e]; for (size_t e = 0; e < siz4; e++) { h = 997 * h + ptr[e]; } return h; } void Image::read_file(const wstring& filename, vector& data) { std::ifstream fin; fin.open(filename, std::ios::binary); fin.seekg(0, std::ios::end); int p2 = (int)fin.tellg(); fin.seekg(0, std::ios::beg); data.resize(p2); fin.read((char*)data.data(), data.size()); fin.close(); } HBITMAP Image::read_png_file(const wstring &filename, int &width, int &height, uint64_t &hash, ImageMethod method) { width = 0; height = 0; PngData inputStream; read_file(filename, inputStream.memory); hash = computeHash(inputStream.memory); return read_png(std::move(inputStream.memory), width, height, method); } HBITMAP Image::read_png_resource(LPCTSTR lpName, LPCTSTR lpType, int &width, int &height, ImageMethod method) { width = 0; height = 0; PngData inputStream; inputStream.memory = loadResourceToMemory(lpName, lpType); if (inputStream.memory.empty()) return nullptr; return read_png(std::move(inputStream.memory), width, height, method); } Image::Image() { } Image::~Image() { } // Loads the PNG containing the splash image into a HBITMAP. HBITMAP Image::loadImage(uint64_t resource, ImageMethod method) { if (images.count(resource)) return images[resource].image; int width, height; HBITMAP hbmp = read_png_resource(MAKEINTRESOURCE(resource), _T("PNG"), width, height, method); if (hbmp != 0) { images[resource].image = hbmp; images[resource].width = width; images[resource].height = height; } return hbmp; } int Image::getWidth(uint64_t resource) { loadImage(resource, ImageMethod::Default); return images[resource].width; } int Image::getHeight(uint64_t resource) { loadImage(resource, ImageMethod::Default); return images[resource].height; } void Image::drawImage(uint64_t resource, ImageMethod method, HDC hDC, int x, int y, int width, int height) { HBITMAP bmp = loadImage(resource, method); auto res = images.find(resource); if (res == images.end()) return; HDC memdc = CreateCompatibleDC(hDC); SelectObject(memdc, bmp); BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; bf.BlendFlags = 0; bf.SourceConstantAlpha =0xFF; bf.AlphaFormat = AC_SRC_ALPHA; AlphaBlend(hDC, x, y, width, height, memdc, 0, 0, res->second.width, res->second.height, bf); DeleteDC(memdc); } uint64_t Image::loadFromFile(const wstring& path, ImageMethod method) { vector bytes; read_file(path, bytes); uint64_t hash = computeHash(bytes); auto res = images.emplace(hash, Bmp()); if (res.second) { wchar_t drive[20]; wchar_t dir[MAX_PATH]; wchar_t name[MAX_PATH]; wchar_t ext[MAX_PATH]; _wsplitpath_s(path.c_str(), drive, dir, name, ext); Bmp &out = res.first->second; out.fileName = wstring(name) + ext; out.rawData = bytes; out.image = read_png(std::move(bytes), out.width, out.height, method); } return hash; } uint64_t Image::loadFromMemory(const wstring& fileName, const vector& bytes, ImageMethod method) { uint64_t hash = computeHash(bytes); return hash; } void Image::provideFromMemory(uint64_t id, const wstring& fileName, const vector& bytes) { uint64_t hash = computeHash(bytes); if (id != hash) throw meosException(L"Corrupted image: " + fileName); images[id].fileName = fileName; images[id].rawData = bytes; } void Image::addImage(uint64_t id, const wstring& fileName) { images[id].fileName = fileName; } void Image::reloadImage(uint64_t imgId, ImageMethod method) { auto res = images.find(imgId); if (res != images.end() && res->second.rawData.size() > 0) { auto copy = res->second.rawData; res->second.destroy(); res->second.image = read_png(std::move(copy), res->second.width, res->second.height, method); return; } throw meosException("Unknown image " + itos(imgId)); } void Image::clearLoaded() { for (auto iter = images.begin(); iter != images.end(); ) { if (iter->second.fileName.empty()) ++iter; else { iter = images.erase(iter); } } } Image::Bmp::~Bmp() { destroy(); } void Image::Bmp::destroy() { if (image) { DeleteObject(image); image = nullptr; } } void Image::enumerateImages(vector>& img) const { img.clear(); for (auto& bmp : images) { if (bmp.second.fileName.size() > 0) img.emplace_back(bmp.second.fileName, img.size()); } sort(img.begin(), img.end()); } uint64_t Image::getIdFromEnumeration(int enumerationIx) const { int ix = 0; for (auto& bmp : images) { if (bmp.second.fileName.size() > 0) { if (enumerationIx == ix++) { return bmp.first; } } } throw meosException("Internal error"); } int Image::getEnumerationIxFromId(uint64_t imgId) const { int ix = 0; for (auto& bmp : images) { if (bmp.second.fileName.size() > 0) { if (imgId == bmp.first) return ix; ix++; } } return -1; } const wstring& Image::getFileName(uint64_t imgId) const { if (!hasImage(imgId)) throw meosException("Missing image: " + itos(imgId)); return images.at(imgId).fileName; } const vector& Image::getRawData(uint64_t imgId) const { if (!hasImage(imgId)) throw meosException("Missing image: " + itos(imgId)); return images.at(imgId).rawData; } bool Image::hasImage(uint64_t imgId) const { auto res = images.find(imgId); return res != images.end() && res->second.rawData.size() > 0; }