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 "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)

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
};

View File

@ -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>);

View File

@ -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)
{

View File

@ -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");

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);