Refactor PNG and working with alpha
This commit is contained in:
parent
b26a1b4a88
commit
4b70eeab55
Binary file not shown.
186
src/Format.cpp
186
src/Format.cpp
@ -1,13 +1,14 @@
|
||||
#include "Format.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <cstring>
|
||||
#include <zlib.h>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <png.h>
|
||||
#include "Format.h"
|
||||
#include "graphics/Graphics.h"
|
||||
|
||||
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat)
|
||||
{
|
||||
@ -94,18 +95,18 @@ String format::CleanString(String dirtyString, bool ascii, bool color, bool newl
|
||||
return dirtyString;
|
||||
}
|
||||
|
||||
std::vector<char> format::VideoBufferToPPM(VideoBuffer const &vidBuf)
|
||||
std::vector<char> format::PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &input)
|
||||
{
|
||||
std::vector<char> data;
|
||||
char buffer[256];
|
||||
sprintf(buffer, "P6\n%d %d\n255\n", vidBuf.Size().X, vidBuf.Size().Y);
|
||||
sprintf(buffer, "P6\n%d %d\n255\n", input.Size().X, input.Size().Y);
|
||||
data.insert(data.end(), buffer, buffer + strlen(buffer));
|
||||
|
||||
data.reserve(data.size() + vidBuf.Size().X * vidBuf.Size().Y * 3);
|
||||
data.reserve(data.size() + input.Size().X * input.Size().Y * 3);
|
||||
|
||||
for (int i = 0; i < vidBuf.Size().X * vidBuf.Size().Y; i++)
|
||||
for (int i = 0; i < input.Size().X * input.Size().Y; i++)
|
||||
{
|
||||
auto colour = RGB<uint8_t>::Unpack(vidBuf.Data()[i]);
|
||||
auto colour = RGB<uint8_t>::Unpack(input.data()[i]);
|
||||
data.push_back(colour.Red);
|
||||
data.push_back(colour.Green);
|
||||
data.push_back(colour.Blue);
|
||||
@ -114,6 +115,167 @@ std::vector<char> format::VideoBufferToPPM(VideoBuffer const &vidBuf)
|
||||
return data;
|
||||
}
|
||||
|
||||
static std::unique_ptr<PlaneAdapter<std::vector<uint32_t>>> readPNG(
|
||||
std::vector<char> const &data,
|
||||
// If omitted,
|
||||
// RGB data is returned with A=0xFF
|
||||
// RGBA data is returned as itself
|
||||
// If specified
|
||||
// RGB data is returned with A=0x00
|
||||
// RGBA data is blended against the background and returned with A=0x00
|
||||
std::optional<RGB<uint8_t>> background
|
||||
)
|
||||
{
|
||||
png_infop info = nullptr;
|
||||
auto deleter = [&info](png_struct *png) {
|
||||
png_destroy_read_struct(&png, &info, NULL);
|
||||
};
|
||||
auto png = std::unique_ptr<png_struct, decltype(deleter)>(
|
||||
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
|
||||
[](png_structp png, png_const_charp msg) {
|
||||
fprintf(stderr, "PNG error: %s\n", msg);
|
||||
},
|
||||
[](png_structp png, png_const_charp msg) {
|
||||
fprintf(stderr, "PNG warning: %s\n", msg);
|
||||
}
|
||||
), deleter
|
||||
);
|
||||
if (!png)
|
||||
return nullptr;
|
||||
|
||||
// libpng might longjmp() here in case of error
|
||||
// Every time we create an object with a non-trivial destructor we must call setjmp again
|
||||
if (setjmp(png_jmpbuf(png.get())))
|
||||
return nullptr;
|
||||
|
||||
info = png_create_info_struct(png.get());
|
||||
if (!info)
|
||||
return nullptr;
|
||||
|
||||
auto it = data.begin();
|
||||
auto const end = data.end();
|
||||
auto readFn = [&it, end](png_structp png, png_bytep data, size_t length) {
|
||||
if (size_t(end - it) < length)
|
||||
png_error(png, "Tried to read beyond the buffer");
|
||||
std::copy_n(it, length, data);
|
||||
it += length;
|
||||
};
|
||||
|
||||
// See above
|
||||
if (setjmp(png_jmpbuf(png.get())))
|
||||
return nullptr;
|
||||
|
||||
png_set_read_fn(png.get(), static_cast<void *>(&readFn), [](png_structp png, png_bytep data, size_t length) {
|
||||
(*static_cast<decltype(readFn) *>(png_get_io_ptr(png)))(png, data, length);
|
||||
});
|
||||
png_set_user_limits(png.get(), RES.X, RES.Y); // Refuse to parse larger images
|
||||
png_read_info(png.get(), info);
|
||||
|
||||
auto output = std::make_unique<PlaneAdapter<std::vector<uint32_t>>>(
|
||||
Vec2<int>(png_get_image_width(png.get(), info), png_get_image_height(png.get(), info))
|
||||
);
|
||||
|
||||
std::vector<png_bytep> rowPointers(output->Size().Y);
|
||||
for (int y = 0; y < output->Size().Y; y++)
|
||||
rowPointers[y] = reinterpret_cast<png_bytep>(&*output->RowIterator(Vec2(0, y)));
|
||||
|
||||
// See above
|
||||
if (setjmp(png_jmpbuf(png.get())))
|
||||
return nullptr;
|
||||
|
||||
png_set_filler(png.get(), background ? 0x00 : 0xFF, PNG_FILLER_AFTER);
|
||||
png_set_bgr(png.get());
|
||||
|
||||
auto bitDepth = png_get_bit_depth(png.get(), info);
|
||||
auto colorType = png_get_color_type(png.get(), info);
|
||||
if (colorType == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png.get());
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png.get());
|
||||
if (bitDepth == 16)
|
||||
png_set_scale_16(png.get());
|
||||
if (png_get_valid(png.get(), info, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png.get());
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png.get());
|
||||
if (background)
|
||||
{
|
||||
png_color_16 colour;
|
||||
colour.red = background->Red;
|
||||
colour.green = background->Green;
|
||||
colour.blue = background->Blue;
|
||||
png_set_background(png.get(), &colour, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
|
||||
}
|
||||
|
||||
png_read_image(png.get(), rowPointers.data());
|
||||
return output;
|
||||
}
|
||||
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> format::PixelsFromPNG(std::vector<char> const &data)
|
||||
{
|
||||
return readPNG(data, std::nullopt);
|
||||
}
|
||||
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel>>> format::PixelsFromPNG(std::vector<char> const &data, RGB<uint8_t> background)
|
||||
{
|
||||
return readPNG(data, background);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<char>> format::PixelsToPNG(PlaneAdapter<std::vector<pixel>> const &input)
|
||||
{
|
||||
png_infop info = nullptr;
|
||||
auto deleter = [&info](png_struct *png) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
};
|
||||
auto png = std::unique_ptr<png_struct, decltype(deleter)>(
|
||||
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
|
||||
[](png_structp png, png_const_charp msg) {
|
||||
fprintf(stderr, "PNG error: %s\n", msg);
|
||||
},
|
||||
[](png_structp png, png_const_charp msg) {
|
||||
fprintf(stderr, "PNG warning: %s\n", msg);
|
||||
}
|
||||
), deleter
|
||||
);
|
||||
if (!png)
|
||||
return nullptr;
|
||||
|
||||
// libpng might longjmp() here in case of error
|
||||
// Every time we create an object with a non-trivial destructor we must call setjmp again
|
||||
if (setjmp(png_jmpbuf(png.get())))
|
||||
return nullptr;
|
||||
|
||||
|
||||
info = png_create_info_struct(png.get());
|
||||
if (!info)
|
||||
return nullptr;
|
||||
|
||||
std::vector<char> output;
|
||||
auto writeFn = [&output](png_structp png, png_bytep data, size_t length) {
|
||||
output.insert(output.end(), data, data + length);
|
||||
};
|
||||
|
||||
std::vector<png_const_bytep> rowPointers(input.Size().Y);
|
||||
for (int y = 0; y < input.Size().Y; y++)
|
||||
rowPointers[y] = reinterpret_cast<png_const_bytep>(&*input.RowIterator(Vec2(0, y)));
|
||||
|
||||
// See above
|
||||
if (setjmp(png_jmpbuf(png.get())))
|
||||
return nullptr;
|
||||
|
||||
png_set_write_fn(png.get(), static_cast<void *>(&writeFn), [](png_structp png, png_bytep data, size_t length) {
|
||||
(*static_cast<decltype(writeFn) *>(png_get_io_ptr(png)))(png, data, length);
|
||||
}, NULL);
|
||||
png_set_IHDR(png.get(), info, input.Size().X, input.Size().Y, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_write_info(png.get(), info);
|
||||
png_set_filler(png.get(), 0x00, PNG_FILLER_AFTER);
|
||||
png_set_bgr(png.get());
|
||||
png_write_image(png.get(), const_cast<png_bytepp>(rowPointers.data()));
|
||||
png_write_end(png.get(), NULL);
|
||||
|
||||
return std::make_unique<std::vector<char>>(std::move(output));
|
||||
}
|
||||
|
||||
const static char hex[] = "0123456789ABCDEF";
|
||||
|
||||
ByteString format::URLEncode(ByteString source)
|
||||
|
10
src/Format.h
10
src/Format.h
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/String.h"
|
||||
#include "common/Plane.h"
|
||||
#include "graphics/Pixel.h"
|
||||
|
||||
class VideoBuffer;
|
||||
|
||||
@ -11,7 +14,10 @@ namespace format
|
||||
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"));
|
||||
ByteString UnixtimeToDateMini(time_t unixtime);
|
||||
String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false);
|
||||
std::vector<char> VideoBufferToPPM(const VideoBuffer & vidBuf);
|
||||
std::vector<char> PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &);
|
||||
std::unique_ptr<std::vector<char>> PixelsToPNG(PlaneAdapter<std::vector<pixel>> const &);
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> PixelsFromPNG(std::vector<char> const &);
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel>>> PixelsFromPNG(std::vector<char> const &, RGB<uint8_t> background);
|
||||
void RenderTemperature(StringBuilder &sb, float temp, int scale);
|
||||
float StringToTemperature(String str, int defaultScale);
|
||||
}
|
||||
|
@ -70,6 +70,6 @@ int main(int argc, char *argv[])
|
||||
ren->RenderBegin();
|
||||
ren->RenderEnd();
|
||||
|
||||
VideoBuffer screenBuffer = ren->DumpFrame();
|
||||
screenBuffer.WritePNG(outputFilename);
|
||||
if (auto data = ren->DumpFrame().ToPNG())
|
||||
Platform::WriteFile(*data, outputFilename);
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
#include "WindowIcon.h"
|
||||
#include "Format.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include "WindowIcon.h"
|
||||
|
||||
#include "icon_exe.png.h"
|
||||
|
||||
void WindowIcon(SDL_Window *window)
|
||||
{
|
||||
std::vector<pixel> imageData;
|
||||
int imgw, imgh;
|
||||
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_exe_png), icon_exe_png_size, false))
|
||||
if (auto image = format::PixelsFromPNG(std::vector<char>(icon_exe_png, icon_exe_png + icon_exe_png_size)))
|
||||
{
|
||||
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
|
||||
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(image->data(), image->Size().X, image->Size().Y, 32, image->Size().Y * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
|
||||
SDL_SetWindowIcon(window, icon);
|
||||
SDL_FreeSurface(icon);
|
||||
}
|
||||
|
@ -23,18 +23,14 @@ namespace http
|
||||
std::unique_ptr<VideoBuffer> vb;
|
||||
if (data.size())
|
||||
{
|
||||
int imgw, imgh;
|
||||
std::vector<pixel> imageData;
|
||||
if (PngDataToPixels(imageData, imgw, imgh, data.data(), data.size(), true))
|
||||
{
|
||||
vb = std::make_unique<VideoBuffer>(imageData.data(), Vec2(imgw, imgh));
|
||||
}
|
||||
vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
|
||||
if (vb)
|
||||
vb->Resize(size, true);
|
||||
else
|
||||
{
|
||||
vb = std::make_unique<VideoBuffer>(Vec2(32, 32));
|
||||
vb->BlendChar(Vec2(14, 14), 'x', 0xFFFFFF_rgb .WithAlpha(0xFF));
|
||||
vb = std::make_unique<VideoBuffer>(Vec2(15, 16));
|
||||
vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF));
|
||||
}
|
||||
vb->Resize(size, true);
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <png.h>
|
||||
#include "common/platform/Platform.h"
|
||||
#include "FontReader.h"
|
||||
#include "Format.h"
|
||||
#include "Graphics.h"
|
||||
#include "resampler/resampler.h"
|
||||
#include "SimulationConfig.h"
|
||||
@ -138,8 +139,34 @@ void VideoBuffer::ResizeToFit(Vec2<int> bound, bool resample)
|
||||
Resize(size, resample);
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoBuffer> VideoBuffer::FromPNG(std::vector<char> const &data)
|
||||
{
|
||||
auto video = format::PixelsFromPNG(data, 0x000000_rgb);
|
||||
if (video)
|
||||
{
|
||||
auto buf = std::make_unique<VideoBuffer>(Vec2<int>::Zero);
|
||||
buf->video = std::move(*video);
|
||||
return buf;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<char>> VideoBuffer::ToPNG() const
|
||||
{
|
||||
return format::PixelsToPNG(video);
|
||||
}
|
||||
|
||||
std::vector<char> VideoBuffer::ToPPM() const
|
||||
{
|
||||
return format::PixelsToPPM(video);
|
||||
}
|
||||
|
||||
template class RasterDrawMethods<VideoBuffer>;
|
||||
|
||||
Graphics::Graphics()
|
||||
{}
|
||||
|
||||
int Graphics::textwidth(const String &str)
|
||||
{
|
||||
int x = 0;
|
||||
@ -509,22 +536,6 @@ void Graphics::draw_icon(int x, int y, Icon icon, unsigned char alpha, bool inve
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::draw_rgba_image(const pixel *data, int w, int h, int x, int y, float alpha)
|
||||
{
|
||||
for (int j = 0; j < h; j++)
|
||||
{
|
||||
for (int i = 0; i < w; i++)
|
||||
{
|
||||
auto rgba = *(data++);
|
||||
auto a = (rgba >> 24) & 0xFF;
|
||||
auto r = (rgba >> 16) & 0xFF;
|
||||
auto g = (rgba >> 8) & 0xFF;
|
||||
auto b = (rgba ) & 0xFF;
|
||||
addpixel(x+i, y+j, r, g, b, (int)(a*alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoBuffer Graphics::DumpFrame()
|
||||
{
|
||||
VideoBuffer newBuffer(video.Size());
|
||||
@ -548,148 +559,6 @@ void Graphics::SetClipRect(int &x, int &y, int &w, int &h)
|
||||
h = rect.Size().Y;
|
||||
}
|
||||
|
||||
bool VideoBuffer::WritePNG(const ByteString &path) const
|
||||
{
|
||||
std::vector<png_const_bytep> rowPointers(Size().Y);
|
||||
for (auto y = 0; y < Size().Y; ++y)
|
||||
{
|
||||
rowPointers[y] = (png_const_bytep)&*video.RowIterator(Vec2(0, y));
|
||||
}
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (!png)
|
||||
{
|
||||
std::cerr << "WritePNG: png_create_write_struct failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (!info)
|
||||
{
|
||||
std::cerr << "WritePNG: png_create_info_struct failed" << std::endl;
|
||||
png_destroy_write_struct(&png, (png_infopp)NULL);
|
||||
return false;
|
||||
}
|
||||
if (setjmp(png_jmpbuf(png)))
|
||||
{
|
||||
// libpng longjmp'd here in its infinite widsom, clean up and return
|
||||
std::cerr << "WritePNG: longjmp from within libpng" << std::endl;
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return false;
|
||||
}
|
||||
struct InMemoryFile
|
||||
{
|
||||
std::vector<char> data;
|
||||
} imf;
|
||||
png_set_write_fn(png, (png_voidp)&imf, [](png_structp png, png_bytep data, size_t length) -> void {
|
||||
auto ud = png_get_io_ptr(png);
|
||||
auto &imf = *(InMemoryFile *)ud;
|
||||
imf.data.insert(imf.data.end(), data, data + length);
|
||||
}, NULL);
|
||||
png_set_IHDR(png, info, Size().X, Size().Y, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_write_info(png, info);
|
||||
png_set_filler(png, 0, PNG_FILLER_AFTER);
|
||||
png_set_bgr(png);
|
||||
png_write_image(png, (png_bytepp)&rowPointers[0]);
|
||||
png_write_end(png, NULL);
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return Platform::WriteFile(imf.data, path);
|
||||
}
|
||||
|
||||
bool PngDataToPixels(std::vector<pixel> &imageData, int &imgw, int &imgh, const char *pngData, size_t pngDataSize, bool addBackground)
|
||||
{
|
||||
std::vector<png_const_bytep> rowPointers;
|
||||
struct InMemoryFile
|
||||
{
|
||||
png_const_bytep data;
|
||||
size_t size;
|
||||
size_t cursor;
|
||||
} imf{ (png_const_bytep)pngData, pngDataSize, 0 };
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (!png)
|
||||
{
|
||||
std::cerr << "pngDataToPixels: png_create_read_struct failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (!info)
|
||||
{
|
||||
std::cerr << "pngDataToPixels: png_create_info_struct failed" << std::endl;
|
||||
png_destroy_read_struct(&png, (png_infopp)NULL, (png_infopp)NULL);
|
||||
return false;
|
||||
}
|
||||
if (setjmp(png_jmpbuf(png)))
|
||||
{
|
||||
// libpng longjmp'd here in its infinite widsom, clean up and return
|
||||
std::cerr << "pngDataToPixels: longjmp from within libpng" << std::endl;
|
||||
png_destroy_read_struct(&png, &info, (png_infopp)NULL);
|
||||
return false;
|
||||
}
|
||||
png_set_read_fn(png, (png_voidp)&imf, [](png_structp png, png_bytep data, size_t length) -> void {
|
||||
auto ud = png_get_io_ptr(png);
|
||||
auto &imf = *(InMemoryFile *)ud;
|
||||
if (length + imf.cursor > imf.size)
|
||||
{
|
||||
png_error(png, "pngDataToPixels: libpng tried to read beyond the buffer");
|
||||
}
|
||||
std::copy(imf.data + imf.cursor, imf.data + imf.cursor + length, data);
|
||||
imf.cursor += length;
|
||||
});
|
||||
png_set_user_limits(png, 1000, 1000);
|
||||
png_read_info(png, info);
|
||||
imgw = png_get_image_width(png, info);
|
||||
imgh = png_get_image_height(png, info);
|
||||
int bitDepth = png_get_bit_depth(png, info);
|
||||
int colorType = png_get_color_type(png, info);
|
||||
imageData.resize(imgw * imgh);
|
||||
rowPointers.resize(imgh);
|
||||
for (auto y = 0; y < imgh; ++y)
|
||||
{
|
||||
rowPointers[y] = (png_const_bytep)&imageData[y * imgw];
|
||||
}
|
||||
if (setjmp(png_jmpbuf(png)))
|
||||
{
|
||||
// libpng longjmp'd here in its infinite widsom, clean up and return
|
||||
std::cerr << "pngDataToPixels: longjmp from within libpng" << std::endl;
|
||||
png_destroy_read_struct(&png, &info, (png_infopp)NULL);
|
||||
return false;
|
||||
}
|
||||
if (addBackground)
|
||||
{
|
||||
png_set_filler(png, 0, PNG_FILLER_AFTER);
|
||||
}
|
||||
png_set_bgr(png);
|
||||
if (colorType == PNG_COLOR_TYPE_PALETTE)
|
||||
{
|
||||
png_set_palette_to_rgb(png);
|
||||
}
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 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);
|
||||
}
|
||||
if (bitDepth == 16)
|
||||
{
|
||||
png_set_scale_16(png);
|
||||
}
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
{
|
||||
png_set_gray_to_rgb(png);
|
||||
}
|
||||
if (addBackground)
|
||||
{
|
||||
png_color_16 defaultBackground;
|
||||
defaultBackground.red = 0;
|
||||
defaultBackground.green = 0;
|
||||
defaultBackground.blue = 0;
|
||||
png_set_background(png, &defaultBackground, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
|
||||
}
|
||||
png_read_image(png, (png_bytepp)&rowPointers[0]);
|
||||
png_destroy_read_struct(&png, &info, (png_infopp)NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Graphics::GradientStop::operator <(const GradientStop &other) const
|
||||
{
|
||||
return point < other.point;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/Plane.h"
|
||||
#include "common/String.h"
|
||||
@ -48,7 +49,9 @@ public:
|
||||
// Automatically choose a size to fit within the given box, keeping aspect ratio
|
||||
void ResizeToFit(Vec2<int> bound, bool resample = false);
|
||||
|
||||
bool WritePNG(const ByteString &path) const;
|
||||
static std::unique_ptr<VideoBuffer> FromPNG(std::vector<char> const &);
|
||||
std::unique_ptr<std::vector<char>> ToPNG() const;
|
||||
std::vector<char> ToPPM() const;
|
||||
};
|
||||
|
||||
class Graphics: public RasterDrawMethods<Graphics>
|
||||
@ -93,14 +96,9 @@ public:
|
||||
|
||||
void Finalise();
|
||||
|
||||
void draw_rgba_image(const pixel *data, int w, int h, int x, int y, float alpha);
|
||||
|
||||
Graphics()
|
||||
{}
|
||||
Graphics();
|
||||
|
||||
void SwapClipRect(Rect<int> &);
|
||||
[[deprecated("Use SwapClipRect")]]
|
||||
void SetClipRect(int &x, int &y, int &w, int &h);
|
||||
};
|
||||
|
||||
bool PngDataToPixels(std::vector<pixel> &imageData, int &imgw, int &imgh, const char *pngData, size_t pngDataSize, bool addBackground);
|
||||
|
@ -11,7 +11,13 @@
|
||||
typedef uint32_t pixel;
|
||||
|
||||
constexpr int PIXELCHANNELS = 3;
|
||||
[[deprecated("Avoid manipulating pixel* as char*")]]
|
||||
constexpr int PIXELSIZE = 4;
|
||||
|
||||
// Least significant byte is blue, then green, then red, then alpha.
|
||||
// Use sparingly, e.g. when passing packed data to a third party library.
|
||||
typedef uint32_t pixel_rgba;
|
||||
|
||||
[[deprecated("Use 0x######_rgb .Pack()")]]
|
||||
constexpr pixel PIXPACK(int x)
|
||||
{
|
||||
@ -22,14 +28,17 @@ constexpr pixel PIXRGB(int r, int g, int b)
|
||||
{
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
[[deprecated("Use RGB<uint8_t>::Unpack(...).Red")]]
|
||||
constexpr int PIXR(pixel x)
|
||||
{
|
||||
return (x >> 16) & 0xFF;
|
||||
}
|
||||
[[deprecated("Use RGB<uint8_t>::Unpack(...).Green")]]
|
||||
constexpr int PIXG(pixel x)
|
||||
{
|
||||
return (x >> 8) & 0xFF;
|
||||
}
|
||||
[[deprecated("Use RGB<uint8_t>::Unpack(...).Blue")]]
|
||||
constexpr int PIXB(pixel x)
|
||||
{
|
||||
return x & 0xFF;
|
||||
@ -50,32 +59,52 @@ struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename S> // Avoid referring to the non-intuitive order of components
|
||||
template<typename S> // Disallow brace initialization
|
||||
RGB(std::initializer_list<S>) = delete;
|
||||
|
||||
// Blend and Add get called in tight loops so it's important that they
|
||||
// vectorize well.
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
RGB<T> Blend(RGBA<T> other) const
|
||||
constexpr RGB<T> Blend(RGBA<T> other) const
|
||||
{
|
||||
if (other.Alpha == 0xFF)
|
||||
return other.NoAlpha();
|
||||
// Dividing by 0xFF means the two branches return the same value in the
|
||||
// case that other.Alpha == 0xFF, and the division happens via
|
||||
// multiplication and bitshift anyway, so it vectorizes better than code
|
||||
// that branches in a meaningful way.
|
||||
return RGB<T>(
|
||||
// Technically should divide by 0xFF, but >> 8 is close enough
|
||||
(other.Alpha * other.Red + (0xFF - other.Alpha) * Red ) >> 8,
|
||||
(other.Alpha * other.Green + (0xFF - other.Alpha) * Green) >> 8,
|
||||
(other.Alpha * other.Blue + (0xFF - other.Alpha) * Blue ) >> 8
|
||||
// the intermediate is guaranteed to fit in 16 bits, and a 16 bit
|
||||
// multiplication vectorizes better than a longer one.
|
||||
uint16_t(other.Alpha * other.Red + (0xFF - other.Alpha) * Red ) / 0xFF,
|
||||
uint16_t(other.Alpha * other.Green + (0xFF - other.Alpha) * Green) / 0xFF,
|
||||
uint16_t(other.Alpha * other.Blue + (0xFF - other.Alpha) * Blue ) / 0xFF
|
||||
);
|
||||
}
|
||||
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
RGB<T> Add(RGBA<T> other) const
|
||||
constexpr RGB<T> Add(RGBA<T> other) const
|
||||
{
|
||||
return RGB<T>(
|
||||
std::min(0xFF, (other.Alpha * other.Red + 0xFF * Red ) >> 8),
|
||||
std::min(0xFF, (other.Alpha * other.Green + 0xFF * Green) >> 8),
|
||||
std::min(0xFF, (other.Alpha * other.Blue + 0xFF * Blue ) >> 8)
|
||||
std::min(0xFF, Red + uint16_t(other.Alpha * other.Red) / 0xFF),
|
||||
std::min(0xFF, Green + uint16_t(other.Alpha * other.Green) / 0xFF),
|
||||
std::min(0xFF, Blue + uint16_t(other.Alpha * other.Blue) / 0xFF)
|
||||
);
|
||||
}
|
||||
|
||||
// Decrement each component that is nonzero.
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
constexpr RGB<T> Decay() const
|
||||
{
|
||||
// This vectorizes really well.
|
||||
pixel colour = Pack(), mask = colour;
|
||||
mask |= mask >> 4;
|
||||
mask |= mask >> 2;
|
||||
mask |= mask >> 1;
|
||||
mask &= 0x00010101;
|
||||
return Unpack(colour - mask);
|
||||
}
|
||||
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
RGB<T> Inverse() const
|
||||
{
|
||||
@ -88,7 +117,7 @@ struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T))
|
||||
}
|
||||
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
pixel Pack() const
|
||||
constexpr pixel Pack() const
|
||||
{
|
||||
return Red << 16 | Green << 8 | Blue;
|
||||
}
|
||||
@ -127,11 +156,17 @@ struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename S> // Avoid referring to the non-intuitive order of components
|
||||
template<typename S> // Disallow brace initialization
|
||||
RGBA(std::initializer_list<S>) = delete;
|
||||
|
||||
RGB<T> NoAlpha() const
|
||||
constexpr RGB<T> NoAlpha() const
|
||||
{
|
||||
return RGB<T>(Red, Green, Blue);
|
||||
}
|
||||
|
||||
template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
|
||||
constexpr static RGBA<T> Unpack(pixel_rgba px)
|
||||
{
|
||||
return RGBA<T>(px >> 16, px >> 8, px, px >> 24);
|
||||
}
|
||||
};
|
||||
|
@ -37,6 +37,9 @@ struct RasterDrawMethods
|
||||
void XorImage(unsigned char const *, Rect<int>);
|
||||
void XorImage(unsigned char const *, Rect<int>, size_t rowStride);
|
||||
|
||||
void BlendRGBAImage(pixel_rgba const *, Rect<int>);
|
||||
void BlendRGBAImage(pixel_rgba const *, Rect<int>, size_t rowStride);
|
||||
|
||||
// Returns width of character
|
||||
int BlendChar(Vec2<int>, String::value_type, RGBA<uint8_t>);
|
||||
int AddChar(Vec2<int>, String::value_type, RGBA<uint8_t>);
|
||||
|
@ -201,6 +201,24 @@ void RasterDrawMethods<Derived>::XorImage(unsigned char const *data, Rect<int> r
|
||||
xorPixelUnchecked(*this, &Derived::video, pos);
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
void RasterDrawMethods<Derived>::BlendRGBAImage(pixel_rgba const *data, Rect<int> rect)
|
||||
{
|
||||
BlendRGBAImage(data, rect, rect.Size().X);
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
void RasterDrawMethods<Derived>::BlendRGBAImage(pixel_rgba const *data, Rect<int> rect, size_t rowStride)
|
||||
{
|
||||
auto origin = rect.TopLeft;
|
||||
rect &= clipRect();
|
||||
for (auto pos : rect)
|
||||
{
|
||||
pixel const px = data[(pos.X - origin.X) + (pos.Y - origin.Y) * rowStride];
|
||||
blendPixelUnchecked(*this, &Derived::video, pos, RGBA<uint8_t>::Unpack(px));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
int RasterDrawMethods<Derived>::BlendChar(Vec2<int> pos, String::value_type ch, RGBA<uint8_t> colour)
|
||||
{
|
||||
|
@ -968,7 +968,7 @@ ByteString GameView::TakeScreenshot(int captureUI, int fileType)
|
||||
else if (fileType == 2)
|
||||
{
|
||||
filename += ".ppm";
|
||||
if (!Platform::WriteFile(format::VideoBufferToPPM(*screenshot), filename))
|
||||
if (!Platform::WriteFile(screenshot->ToPPM(), filename))
|
||||
{
|
||||
filename = "";
|
||||
}
|
||||
@ -976,10 +976,13 @@ ByteString GameView::TakeScreenshot(int captureUI, int fileType)
|
||||
else
|
||||
{
|
||||
filename += ".png";
|
||||
if (!screenshot->WritePNG(filename))
|
||||
if (auto data = screenshot->ToPNG())
|
||||
{
|
||||
if (!Platform::WriteFile(*data, filename))
|
||||
filename = "";
|
||||
}
|
||||
else
|
||||
filename = "";
|
||||
}
|
||||
|
||||
return filename;
|
||||
@ -2179,8 +2182,7 @@ void GameView::OnDraw()
|
||||
|
||||
if(recording)
|
||||
{
|
||||
VideoBuffer screenshot(ren->DumpFrame());
|
||||
std::vector<char> data = format::VideoBufferToPPM(screenshot);
|
||||
std::vector<char> data = ren->DumpFrame().ToPPM();
|
||||
|
||||
ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm");
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include "gui/interface/Label.h"
|
||||
#include "gui/interface/Textbox.h"
|
||||
|
||||
#include "save_local.png.h"
|
||||
#include "Config.h"
|
||||
|
||||
LocalSaveActivity::LocalSaveActivity(SaveFile save, OnSaved onSaved_) :
|
||||
@ -22,8 +21,6 @@ LocalSaveActivity::LocalSaveActivity(SaveFile save, OnSaved onSaved_) :
|
||||
thumbnailRenderer(nullptr),
|
||||
onSaved(onSaved_)
|
||||
{
|
||||
PngDataToPixels(save_to_disk_image, save_to_disk_imageW, save_to_disk_imageH, reinterpret_cast<const char *>(save_local_png), save_local_png_size, false);
|
||||
|
||||
ui::Label * titleLabel = new ui::Label(ui::Point(4, 5), ui::Point(Size.X-8, 16), "Save to computer:");
|
||||
titleLabel->SetTextColour(style::Colour::InformationTitle);
|
||||
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
@ -134,7 +131,7 @@ void LocalSaveActivity::saveWrite(ByteString finalFilename)
|
||||
void LocalSaveActivity::OnDraw()
|
||||
{
|
||||
Graphics * g = GetGraphics();
|
||||
g->draw_rgba_image(&save_to_disk_image[0], save_to_disk_imageW, save_to_disk_imageH, 0, 0, 0.7f);
|
||||
g->BlendRGBAImage(saveToDiskImage->data(), RectSized(Vec2(0, 0), saveToDiskImage->Size()));
|
||||
g->DrawFilledRect(RectSized(Position, Size).Inset(-1), 0x000000_rgb);
|
||||
g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb);
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Activity.h"
|
||||
#include "client/SaveFile.h"
|
||||
#include "graphics/Pixel.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "Activity.h"
|
||||
#include "client/SaveFile.h"
|
||||
#include "common/Plane.h"
|
||||
#include "Format.h"
|
||||
#include "graphics/Pixel.h"
|
||||
|
||||
#include "save_local.png.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
@ -20,8 +23,9 @@ class ThumbnailRendererTask;
|
||||
class LocalSaveActivity: public WindowActivity
|
||||
{
|
||||
using OnSaved = std::function<void (SaveFile *)>;
|
||||
std::vector<pixel> save_to_disk_image;
|
||||
int save_to_disk_imageW, save_to_disk_imageH;
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> saveToDiskImage = format::PixelsFromPNG(
|
||||
std::vector<char>(save_local_png, save_local_png + save_local_png_size)
|
||||
);
|
||||
|
||||
SaveFile save;
|
||||
ThumbnailRendererTask *thumbnailRenderer;
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
#include "gui/Style.h"
|
||||
|
||||
#include "save_online.png.h"
|
||||
|
||||
class SaveUploadTask: public Task
|
||||
{
|
||||
SaveInfo save;
|
||||
@ -61,8 +59,6 @@ ServerSaveActivity::ServerSaveActivity(SaveInfo save, OnUploaded onUploaded_) :
|
||||
onUploaded(onUploaded_),
|
||||
saveUploadTask(NULL)
|
||||
{
|
||||
PngDataToPixels(save_to_server_image, save_to_server_imageW, save_to_server_imageH, reinterpret_cast<const char *>(save_online_png), save_online_png_size, false);
|
||||
|
||||
titleLabel = new ui::Label(ui::Point(4, 5), ui::Point((Size.X/2)-8, 16), "");
|
||||
titleLabel->SetTextColour(style::Colour::InformationTitle);
|
||||
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
@ -159,8 +155,6 @@ ServerSaveActivity::ServerSaveActivity(SaveInfo save, bool saveNow, OnUploaded o
|
||||
onUploaded(onUploaded_),
|
||||
saveUploadTask(NULL)
|
||||
{
|
||||
PngDataToPixels(save_to_server_image, save_to_server_imageW, save_to_server_imageH, reinterpret_cast<const char *>(save_online_png), save_online_png_size, false);
|
||||
|
||||
ui::Label * titleLabel = new ui::Label(ui::Point(0, 0), Size, "Saving to server...");
|
||||
titleLabel->SetTextColour(style::Colour::InformationTitle);
|
||||
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignCentre;
|
||||
@ -374,7 +368,7 @@ void ServerSaveActivity::OnTick(float dt)
|
||||
void ServerSaveActivity::OnDraw()
|
||||
{
|
||||
Graphics * g = GetGraphics();
|
||||
g->draw_rgba_image(&save_to_server_image[0], save_to_server_imageW, save_to_server_imageH, -10, 0, 0.7f);
|
||||
g->BlendRGBAImage(saveToServerImage->data(), RectSized(Vec2(-10, 0), saveToServerImage->Size()));
|
||||
g->DrawFilledRect(RectSized(Position, Size).Inset(-1), 0x000000_rgb);
|
||||
g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb);
|
||||
|
||||
|
@ -1,13 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Activity.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "tasks/TaskListener.h"
|
||||
#include "common/Plane.h"
|
||||
#include "Format.h"
|
||||
#include "graphics/Pixel.h"
|
||||
#include "tasks/TaskListener.h"
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "save_online.png.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
@ -22,8 +26,9 @@ class VideoBuffer;
|
||||
class ServerSaveActivity: public WindowActivity, public TaskListener
|
||||
{
|
||||
using OnUploaded = std::function<void (SaveInfo &)>;
|
||||
std::vector<pixel> save_to_server_image;
|
||||
int save_to_server_imageW, save_to_server_imageH;
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> saveToServerImage = format::PixelsFromPNG(
|
||||
std::vector<char>(save_online_png, save_online_png + save_online_png_size)
|
||||
);
|
||||
|
||||
public:
|
||||
ServerSaveActivity(SaveInfo save, OnUploaded onUploaded);
|
||||
|
Loading…
Reference in New Issue
Block a user