Refactor PNG and working with alpha

This commit is contained in:
mniip
2023-04-05 20:24:48 +02:00
parent b26a1b4a88
commit 4b70eeab55
16 changed files with 326 additions and 237 deletions

Binary file not shown.

View File

@@ -1,13 +1,14 @@
#include "Format.h" #include <cstdint>
#include "graphics/Graphics.h" #include <cstdio>
#include <cstring>
#include <ctime> #include <ctime>
#include <stdexcept>
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
#include <cstring> #include <optional>
#include <zlib.h> #include <stdexcept>
#include <cstdio> #include <png.h>
#include <cstdint> #include "Format.h"
#include "graphics/Graphics.h"
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat) 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; 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; std::vector<char> data;
char buffer[256]; 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.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.Red);
data.push_back(colour.Green); data.push_back(colour.Green);
data.push_back(colour.Blue); data.push_back(colour.Blue);
@@ -114,6 +115,167 @@ std::vector<char> format::VideoBufferToPPM(VideoBuffer const &vidBuf)
return data; 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"; const static char hex[] = "0123456789ABCDEF";
ByteString format::URLEncode(ByteString source) ByteString format::URLEncode(ByteString source)

View File

@@ -1,6 +1,9 @@
#pragma once #pragma once
#include "common/String.h" #include <memory>
#include <vector> #include <vector>
#include "common/String.h"
#include "common/Plane.h"
#include "graphics/Pixel.h"
class VideoBuffer; class VideoBuffer;
@@ -11,7 +14,10 @@ namespace format
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y")); ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"));
ByteString UnixtimeToDateMini(time_t unixtime); ByteString UnixtimeToDateMini(time_t unixtime);
String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false); 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); void RenderTemperature(StringBuilder &sb, float temp, int scale);
float StringToTemperature(String str, int defaultScale); float StringToTemperature(String str, int defaultScale);
} }

View File

@@ -70,6 +70,6 @@ int main(int argc, char *argv[])
ren->RenderBegin(); ren->RenderBegin();
ren->RenderEnd(); ren->RenderEnd();
VideoBuffer screenBuffer = ren->DumpFrame(); if (auto data = ren->DumpFrame().ToPNG())
screenBuffer.WritePNG(outputFilename); Platform::WriteFile(*data, outputFilename);
} }

View File

@@ -1,14 +1,14 @@
#include "WindowIcon.h" #include "Format.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "WindowIcon.h"
#include "icon_exe.png.h" #include "icon_exe.png.h"
void WindowIcon(SDL_Window *window) void WindowIcon(SDL_Window *window)
{ {
std::vector<pixel> imageData; if (auto image = format::PixelsFromPNG(std::vector<char>(icon_exe_png, icon_exe_png + icon_exe_png_size)))
int imgw, imgh;
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_exe_png), icon_exe_png_size, false))
{ {
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_SetWindowIcon(window, icon);
SDL_FreeSurface(icon); SDL_FreeSurface(icon);
} }

View File

@@ -23,18 +23,14 @@ namespace http
std::unique_ptr<VideoBuffer> vb; std::unique_ptr<VideoBuffer> vb;
if (data.size()) if (data.size())
{ {
int imgw, imgh; vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
std::vector<pixel> imageData; if (vb)
if (PngDataToPixels(imageData, imgw, imgh, data.data(), data.size(), true)) vb->Resize(size, true);
{
vb = std::make_unique<VideoBuffer>(imageData.data(), Vec2(imgw, imgh));
}
else else
{ {
vb = std::make_unique<VideoBuffer>(Vec2(32, 32)); vb = std::make_unique<VideoBuffer>(Vec2(15, 16));
vb->BlendChar(Vec2(14, 14), 'x', 0xFFFFFF_rgb .WithAlpha(0xFF)); vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF));
} }
vb->Resize(size, true);
} }
return vb; return vb;
} }

View File

@@ -7,6 +7,7 @@
#include <png.h> #include <png.h>
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include "FontReader.h" #include "FontReader.h"
#include "Format.h"
#include "Graphics.h" #include "Graphics.h"
#include "resampler/resampler.h" #include "resampler/resampler.h"
#include "SimulationConfig.h" #include "SimulationConfig.h"
@@ -138,8 +139,34 @@ void VideoBuffer::ResizeToFit(Vec2<int> bound, bool resample)
Resize(size, 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>; template class RasterDrawMethods<VideoBuffer>;
Graphics::Graphics()
{}
int Graphics::textwidth(const String &str) int Graphics::textwidth(const String &str)
{ {
int x = 0; 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 Graphics::DumpFrame()
{ {
VideoBuffer newBuffer(video.Size()); VideoBuffer newBuffer(video.Size());
@@ -548,148 +559,6 @@ void Graphics::SetClipRect(int &x, int &y, int &w, int &h)
h = rect.Size().Y; 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 bool Graphics::GradientStop::operator <(const GradientStop &other) const
{ {
return point < other.point; return point < other.point;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
#include "common/Plane.h" #include "common/Plane.h"
#include "common/String.h" #include "common/String.h"
@@ -48,7 +49,9 @@ public:
// Automatically choose a size to fit within the given box, keeping aspect ratio // Automatically choose a size to fit within the given box, keeping aspect ratio
void ResizeToFit(Vec2<int> bound, bool resample = false); 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> class Graphics: public RasterDrawMethods<Graphics>
@@ -93,14 +96,9 @@ public:
void Finalise(); 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> &); void SwapClipRect(Rect<int> &);
[[deprecated("Use SwapClipRect")]] [[deprecated("Use SwapClipRect")]]
void SetClipRect(int &x, int &y, int &w, int &h); 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);

View File

@@ -11,7 +11,13 @@
typedef uint32_t pixel; typedef uint32_t pixel;
constexpr int PIXELCHANNELS = 3; constexpr int PIXELCHANNELS = 3;
[[deprecated("Avoid manipulating pixel* as char*")]]
constexpr int PIXELSIZE = 4; 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()")]] [[deprecated("Use 0x######_rgb .Pack()")]]
constexpr pixel PIXPACK(int x) constexpr pixel PIXPACK(int x)
{ {
@@ -22,14 +28,17 @@ constexpr pixel PIXRGB(int r, int g, int b)
{ {
return (r << 16) | (g << 8) | b; return (r << 16) | (g << 8) | b;
} }
[[deprecated("Use RGB<uint8_t>::Unpack(...).Red")]]
constexpr int PIXR(pixel x) constexpr int PIXR(pixel x)
{ {
return (x >> 16) & 0xFF; return (x >> 16) & 0xFF;
} }
[[deprecated("Use RGB<uint8_t>::Unpack(...).Green")]]
constexpr int PIXG(pixel x) constexpr int PIXG(pixel x)
{ {
return (x >> 8) & 0xFF; return (x >> 8) & 0xFF;
} }
[[deprecated("Use RGB<uint8_t>::Unpack(...).Blue")]]
constexpr int PIXB(pixel x) constexpr int PIXB(pixel x)
{ {
return x & 0xFF; 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; 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>>> 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) if (other.Alpha == 0xFF)
return other.NoAlpha(); 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>( return RGB<T>(
// Technically should divide by 0xFF, but >> 8 is close enough // the intermediate is guaranteed to fit in 16 bits, and a 16 bit
(other.Alpha * other.Red + (0xFF - other.Alpha) * Red ) >> 8, // multiplication vectorizes better than a longer one.
(other.Alpha * other.Green + (0xFF - other.Alpha) * Green) >> 8, uint16_t(other.Alpha * other.Red + (0xFF - other.Alpha) * Red ) / 0xFF,
(other.Alpha * other.Blue + (0xFF - other.Alpha) * Blue ) >> 8 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>>> 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>( return RGB<T>(
std::min(0xFF, (other.Alpha * other.Red + 0xFF * Red ) >> 8), std::min(0xFF, Red + uint16_t(other.Alpha * other.Red) / 0xFF),
std::min(0xFF, (other.Alpha * other.Green + 0xFF * Green) >> 8), std::min(0xFF, Green + uint16_t(other.Alpha * other.Green) / 0xFF),
std::min(0xFF, (other.Alpha * other.Blue + 0xFF * Blue ) >> 8) 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>>> template<typename S = T, typename = std::enable_if_t<std::is_same_v<S, uint8_t>>>
RGB<T> Inverse() const 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>>> 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; 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; RGBA(std::initializer_list<S>) = delete;
RGB<T> NoAlpha() const constexpr RGB<T> NoAlpha() const
{ {
return RGB<T>(Red, Green, Blue); 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);
}
}; };

View File

@@ -37,6 +37,9 @@ struct RasterDrawMethods
void XorImage(unsigned char const *, Rect<int>); void XorImage(unsigned char const *, Rect<int>);
void XorImage(unsigned char const *, Rect<int>, size_t rowStride); 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 // Returns width of character
int BlendChar(Vec2<int>, String::value_type, RGBA<uint8_t>); int BlendChar(Vec2<int>, String::value_type, RGBA<uint8_t>);
int AddChar(Vec2<int>, String::value_type, RGBA<uint8_t>); int AddChar(Vec2<int>, String::value_type, RGBA<uint8_t>);

View File

@@ -201,6 +201,24 @@ void RasterDrawMethods<Derived>::XorImage(unsigned char const *data, Rect<int> r
xorPixelUnchecked(*this, &Derived::video, pos); 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> template<typename Derived>
int RasterDrawMethods<Derived>::BlendChar(Vec2<int> pos, String::value_type ch, RGBA<uint8_t> colour) int RasterDrawMethods<Derived>::BlendChar(Vec2<int> pos, String::value_type ch, RGBA<uint8_t> colour)
{ {

View File

@@ -968,7 +968,7 @@ ByteString GameView::TakeScreenshot(int captureUI, int fileType)
else if (fileType == 2) else if (fileType == 2)
{ {
filename += ".ppm"; filename += ".ppm";
if (!Platform::WriteFile(format::VideoBufferToPPM(*screenshot), filename)) if (!Platform::WriteFile(screenshot->ToPPM(), filename))
{ {
filename = ""; filename = "";
} }
@@ -976,10 +976,13 @@ ByteString GameView::TakeScreenshot(int captureUI, int fileType)
else else
{ {
filename += ".png"; filename += ".png";
if (!screenshot->WritePNG(filename)) if (auto data = screenshot->ToPNG())
{ {
filename = ""; if (!Platform::WriteFile(*data, filename))
filename = "";
} }
else
filename = "";
} }
return filename; return filename;
@@ -2179,8 +2182,7 @@ void GameView::OnDraw()
if(recording) if(recording)
{ {
VideoBuffer screenshot(ren->DumpFrame()); std::vector<char> data = ren->DumpFrame().ToPPM();
std::vector<char> data = format::VideoBufferToPPM(screenshot);
ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm"); ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm");

View File

@@ -13,7 +13,6 @@
#include "gui/interface/Label.h" #include "gui/interface/Label.h"
#include "gui/interface/Textbox.h" #include "gui/interface/Textbox.h"
#include "save_local.png.h"
#include "Config.h" #include "Config.h"
LocalSaveActivity::LocalSaveActivity(SaveFile save, OnSaved onSaved_) : LocalSaveActivity::LocalSaveActivity(SaveFile save, OnSaved onSaved_) :
@@ -22,8 +21,6 @@ LocalSaveActivity::LocalSaveActivity(SaveFile save, OnSaved onSaved_) :
thumbnailRenderer(nullptr), thumbnailRenderer(nullptr),
onSaved(onSaved_) 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:"); 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->SetTextColour(style::Colour::InformationTitle);
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
@@ -134,7 +131,7 @@ void LocalSaveActivity::saveWrite(ByteString finalFilename)
void LocalSaveActivity::OnDraw() void LocalSaveActivity::OnDraw()
{ {
Graphics * g = GetGraphics(); 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->DrawFilledRect(RectSized(Position, Size).Inset(-1), 0x000000_rgb);
g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb); g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb);

View File

@@ -1,12 +1,15 @@
#pragma once #pragma once
#include "Activity.h"
#include "client/SaveFile.h"
#include "graphics/Pixel.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #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 namespace ui
{ {
@@ -20,15 +23,16 @@ class ThumbnailRendererTask;
class LocalSaveActivity: public WindowActivity class LocalSaveActivity: public WindowActivity
{ {
using OnSaved = std::function<void (SaveFile *)>; using OnSaved = std::function<void (SaveFile *)>;
std::vector<pixel> save_to_disk_image; std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> saveToDiskImage = format::PixelsFromPNG(
int save_to_disk_imageW, save_to_disk_imageH; std::vector<char>(save_local_png, save_local_png + save_local_png_size)
);
SaveFile save; SaveFile save;
ThumbnailRendererTask *thumbnailRenderer; ThumbnailRendererTask *thumbnailRenderer;
std::unique_ptr<VideoBuffer> thumbnail; std::unique_ptr<VideoBuffer> thumbnail;
ui::Textbox * filenameField; ui::Textbox * filenameField;
OnSaved onSaved; OnSaved onSaved;
public: public:
LocalSaveActivity(SaveFile save, OnSaved onSaved = nullptr); LocalSaveActivity(SaveFile save, OnSaved onSaved = nullptr);
void saveWrite(ByteString finalFilename); void saveWrite(ByteString finalFilename);

View File

@@ -19,8 +19,6 @@
#include "gui/Style.h" #include "gui/Style.h"
#include "save_online.png.h"
class SaveUploadTask: public Task class SaveUploadTask: public Task
{ {
SaveInfo save; SaveInfo save;
@@ -61,8 +59,6 @@ ServerSaveActivity::ServerSaveActivity(SaveInfo save, OnUploaded onUploaded_) :
onUploaded(onUploaded_), onUploaded(onUploaded_),
saveUploadTask(NULL) 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 = new ui::Label(ui::Point(4, 5), ui::Point((Size.X/2)-8, 16), "");
titleLabel->SetTextColour(style::Colour::InformationTitle); titleLabel->SetTextColour(style::Colour::InformationTitle);
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
@@ -159,8 +155,6 @@ ServerSaveActivity::ServerSaveActivity(SaveInfo save, bool saveNow, OnUploaded o
onUploaded(onUploaded_), onUploaded(onUploaded_),
saveUploadTask(NULL) 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..."); ui::Label * titleLabel = new ui::Label(ui::Point(0, 0), Size, "Saving to server...");
titleLabel->SetTextColour(style::Colour::InformationTitle); titleLabel->SetTextColour(style::Colour::InformationTitle);
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignCentre;
@@ -374,7 +368,7 @@ void ServerSaveActivity::OnTick(float dt)
void ServerSaveActivity::OnDraw() void ServerSaveActivity::OnDraw()
{ {
Graphics * g = GetGraphics(); 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->DrawFilledRect(RectSized(Position, Size).Inset(-1), 0x000000_rgb);
g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb); g->DrawRect(RectSized(Position, Size), 0xFFFFFF_rgb);

View File

@@ -1,13 +1,17 @@
#pragma once #pragma once
#include <functional>
#include <memory>
#include <vector>
#include "Activity.h" #include "Activity.h"
#include "client/SaveInfo.h" #include "client/SaveInfo.h"
#include "tasks/TaskListener.h" #include "common/Plane.h"
#include "Format.h"
#include "graphics/Pixel.h" #include "graphics/Pixel.h"
#include "tasks/TaskListener.h"
#include <memory> #include "save_online.png.h"
#include <functional>
#include <vector>
namespace ui namespace ui
{ {
@@ -22,8 +26,9 @@ class VideoBuffer;
class ServerSaveActivity: public WindowActivity, public TaskListener class ServerSaveActivity: public WindowActivity, public TaskListener
{ {
using OnUploaded = std::function<void (SaveInfo &)>; using OnUploaded = std::function<void (SaveInfo &)>;
std::vector<pixel> save_to_server_image; std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> saveToServerImage = format::PixelsFromPNG(
int save_to_server_imageW, save_to_server_imageH; std::vector<char>(save_online_png, save_online_png + save_online_png_size)
);
public: public:
ServerSaveActivity(SaveInfo save, OnUploaded onUploaded); ServerSaveActivity(SaveInfo save, OnUploaded onUploaded);