Remove all PTI code, use libpng to load avatars and thumbnails

Also write PNGs with libpng, and BMPs with SDL, and have the renderer only generate a large PNG thumbnail, and disable HTTP/2 multiplexing for now so we don't get banned when loading avatars.

simon pls reply to the stupid emails already.
This commit is contained in:
Tamás Bálint Misius 2022-08-07 15:27:04 +02:00
parent 3cda085bac
commit 59354731df
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
25 changed files with 301 additions and 4767 deletions

View File

@ -45,9 +45,9 @@ Libraries and other assets used
* [FFTW](http://fftw.org/)
* [JsonCpp](https://github.com/open-source-parsers/jsoncpp)
* [libcurl](https://curl.se/libcurl/)
* [libpng](http://www.libpng.org/pub/png/libpng.html)
* [Lua](https://www.lua.org/)
* [LuaJIT](https://luajit.org/)
* [LuaSocket](http://w3.impa.br/~diego/software/luasocket/)
* [Mallangche](https://github.com/JammPark/Mallangche)
* [mbedtls](https://www.trustedfirmware.org/projects/mbed-tls/)
* [SDL](https://libsdl.org/)

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
/**
* Powder Toy - Main source
*
* Copyright (c) 2008 - 2010 Stanislaw Skowronek.
* Copyright (c) 2010 Simon Robertshaw
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#pragma once
extern const unsigned char app_icon[];

View File

@ -1,6 +1,5 @@
data_files += files(
'hmap.cpp',
'icon.cpp',
'images.cpp',
)
data_files += to_array.process('font.bz2', extra_args: 'compressed_font_data')

View File

@ -171,6 +171,7 @@ fftw_dep = enable_gravfft ? dependency('fftw3f', static: is_static) : []
threads_dep = dependency('threads')
zlib_dep = dependency('zlib', static: is_static)
png_dep = dependency('libpng16', static: is_static)
sdl2_dep = dependency('sdl2', static: is_static)
bzip2_dep = dependency('bzip2', static: is_static)
json_dep = dependency('jsoncpp', static: is_static)
@ -349,6 +350,7 @@ if get_option('build_powder')
powder_deps = [
threads_dep,
zlib_dep,
png_dep,
sdl2_dep,
lua_dep,
curl_dep,
@ -387,14 +389,19 @@ if get_option('build_render')
zlib_dep,
bzip2_dep,
json_dep,
png_dep,
]
render_link_args = project_link_args
if host_platform == 'linux' and is_static
render_link_args += [ '-static' ]
endif
executable(
'render',
sources: render_files,
include_directories: [ project_inc, render_inc ],
c_args: project_c_args,
cpp_args: project_cpp_args,
link_args: project_link_args,
link_args: render_link_args,
dependencies: render_deps,
)
endif
@ -403,6 +410,7 @@ if get_option('build_font')
font_deps = [
threads_dep,
zlib_dep,
png_dep,
sdl2_dep,
bzip2_dep,
json_dep,

View File

@ -1,16 +0,0 @@
import sys
from PIL import Image
image = Image.open(sys.argv[1])
output = ""
formatted = []
for pixel in image.getdata():
formatted.extend("0x{0:02X}".format(byte) for byte in pixel)
for i in range(len(formatted)/16 + 1):
print(", ".join(formatted[i*16:(i+1)*16]) + ",")
"""with open(sys.argv[1], "rb") as icon:
icondata = icon.read()
output = ["0x{0:02X}".format(ord(byte)) for byte in icondata]
for line in range(len(output)/16+1):
print(", ".join(output[line*16:(line+1)*16])+",")"""

View File

@ -9,18 +9,21 @@ if host_platform == 'windows'
)
endif
data_files += to_array.process('cps16.png', extra_args: 'cps16_png')
data_files += to_array.process('cps32.png', extra_args: 'cps32_png')
data_files += to_array.process('exe48.png', extra_args: 'exe48_png')
data_files += to_array.process('save.xml', extra_args: 'save_xml')
data_files += to_array.process(configure_file(
input: 'powder.template.desktop',
output: 'powder.desktop',
configuration: conf_data,
), extra_args: 'powder_desktop')
if host_platform == 'linux'
data_files += to_array.process('cps16.png', extra_args: 'cps16_png')
data_files += to_array.process('cps32.png', extra_args: 'cps32_png')
data_files += to_array.process('exe48.png', extra_args: 'exe48_png')
data_files += to_array.process('icon/powder-128.png', extra_args: 'icon_png')
data_files += to_array.process('save.xml', extra_args: 'save_xml')
data_files += to_array.process(configure_file(
input: 'powder.template.desktop',
output: 'powder.desktop',
configuration: conf_data,
), extra_args: 'powder_desktop')
configure_file(
input: 'appdata.template.xml',
output: 'appdata.xml',
configuration: conf_data,
)
configure_file(
input: 'appdata.template.xml',
output: 'appdata.xml',
configuration: conf_data,
)
endif

View File

@ -10,6 +10,10 @@
#include "graphics/Graphics.h"
#ifndef RENDERER
# include "SDLCompat.h"
#endif
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat)
{
struct tm * timeData;
@ -95,77 +99,6 @@ String format::CleanString(String dirtyString, bool ascii, bool color, bool newl
return dirtyString;
}
std::vector<char> format::VideoBufferToPTI(const VideoBuffer & vidBuf)
{
std::vector<char> data;
int dataSize = 0;
char * buffer = (char*)Graphics::ptif_pack(vidBuf.Buffer, vidBuf.Width, vidBuf.Height, &dataSize);
if(buffer)
{
data.insert(data.end(), buffer, buffer+dataSize);
free(buffer);
}
return data;
}
VideoBuffer * format::PTIToVideoBuffer(std::vector<char> & data)
{
int newWidth, newHeight;
pixel * buffer = Graphics::ptif_unpack(&data[0], data.size(), &newWidth, &newHeight);
if(buffer)
{
VideoBuffer * vb = new VideoBuffer(buffer, newWidth, newHeight);
free(buffer);
return vb;
}
return NULL;
}
std::vector<char> format::VideoBufferToBMP(const VideoBuffer & vidBuf)
{
std::vector<char> data;
char buffer[54] = "BM";
int padding = 3 - (vidBuf.Width * 3 + 3) % 4;
unsigned int fileSize = (vidBuf.Width * 3 + padding) * vidBuf.Height + 54;
*(int *)(buffer + 2) = fileSize;
*(short int *)(buffer + 6) = 0; // reserved;
*(short int *)(buffer + 8) = 0; // reserved;
*(int *)(buffer + 10) = 0x36; // 54 bytes from start to data
*(int *)(buffer + 14) = 0x28; // 40 bytes in info header
*(int *)(buffer + 18) = vidBuf.Width;
*(int *)(buffer + 22) = vidBuf.Height;
*(short int *)(buffer + 26) = 1; // 1 plane
*(short int *)(buffer + 28) = 24; // 24 bits per pixel
*(int *)(buffer + 30) = 0; // no compression
*(int *)(buffer + 34) = fileSize - 54; // bitmap size
*(int *)(buffer + 38) = 0xB13; // 72dpi
*(int *)(buffer + 42) = 0xB13; // 72dpi
*(int *)(buffer + 46) = 0; // all colors used
*(int *)(buffer + 50) = 0; // all colors important
data.insert(data.end(), buffer, buffer+54);
char *currentRow = (char *)malloc((vidBuf.Width * 3) + padding);
for(int y = vidBuf.Height - 1; y >= 0; y--)
{
int rowPos = 0;
for(int x = 0; x < vidBuf.Width; x++)
{
currentRow[rowPos++] = PIXB(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXG(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXR(vidBuf.Buffer[(y*vidBuf.Width)+x]);
}
data.insert(data.end(), currentRow, currentRow+(vidBuf.Width*3)+padding);
}
free(currentRow);
return data;
}
std::vector<char> format::VideoBufferToPPM(const VideoBuffer & vidBuf)
{
std::vector<char> data;
@ -190,264 +123,7 @@ std::vector<char> format::VideoBufferToPPM(const VideoBuffer & vidBuf)
return data;
}
struct PNGChunk
{
int Length;
char Name[4];
char * Data;
//char[4] CRC();
PNGChunk(int length, ByteString name)
{
if (name.length()!=4)
throw std::runtime_error("Invalid chunk name");
std::copy(name.begin(), name.begin()+4, Name);
Length = length;
if (length)
{
Data = new char[length];
std::fill(Data, Data+length, 0);
}
else
{
Data = NULL;
}
}
unsigned long CRC()
{
if (!Data)
{
return format::CalculateCRC((unsigned char*)Name, 4);
}
else
{
unsigned char * temp = new unsigned char[4+Length];
std::copy(Name, Name+4, temp);
std::copy(Data, Data+Length, temp+4);
unsigned long tempRet = format::CalculateCRC(temp, 4+Length);
delete[] temp;
return tempRet;
}
}
~PNGChunk()
{
delete[] Data;
}
};
std::vector<char> format::VideoBufferToPNG(const VideoBuffer & vidBuf)
{
std::vector<PNGChunk*> chunks;
//Begin IHDR (Image header) chunk (Image size and depth)
PNGChunk *IHDRChunk = new PNGChunk(13, "IHDR");
//Image Width
IHDRChunk->Data[0] = (vidBuf.Width>>24)&0xFF;
IHDRChunk->Data[1] = (vidBuf.Width>>16)&0xFF;
IHDRChunk->Data[2] = (vidBuf.Width>>8)&0xFF;
IHDRChunk->Data[3] = (vidBuf.Width)&0xFF;
//Image Height
IHDRChunk->Data[4] = (vidBuf.Height>>24)&0xFF;
IHDRChunk->Data[5] = (vidBuf.Height>>16)&0xFF;
IHDRChunk->Data[6] = (vidBuf.Height>>8)&0xFF;
IHDRChunk->Data[7] = (vidBuf.Height)&0xFF;
//Bit depth
IHDRChunk->Data[8] = 8; //8bits per channel or 24bpp
//Colour type
IHDRChunk->Data[9] = 2; //RGB triple
//Everything else is default
chunks.push_back(IHDRChunk);
//Begin image data, format is 8bit RGB (24bit pixel)
int dataPos = 0;
unsigned char * uncompressedData = new unsigned char[(vidBuf.Width*vidBuf.Height*3)+vidBuf.Height];
//Byte ordering and filtering
unsigned char * previousRow = new unsigned char[vidBuf.Width*3];
std::fill(previousRow, previousRow+(vidBuf.Width*3), 0);
unsigned char * currentRow = new unsigned char[vidBuf.Width*3];
for(int y = 0; y < vidBuf.Height; y++)
{
int rowPos = 0;
for(int x = 0; x < vidBuf.Width; x++)
{
currentRow[rowPos++] = PIXR(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXG(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXB(vidBuf.Buffer[(y*vidBuf.Width)+x]);
}
uncompressedData[dataPos++] = 2; //Up Sub(x) filter
for(int b = 0; b < rowPos; b++)
{
int filteredByte = (currentRow[b]-previousRow[b])&0xFF;
uncompressedData[dataPos++] = filteredByte;
}
unsigned char * tempRow = previousRow;
previousRow = currentRow;
currentRow = tempRow;
}
delete[] currentRow;
delete[] previousRow;
//Compression
int compressedBufferSize = (vidBuf.Width*vidBuf.Height*3)*2;
unsigned char * compressedData = new unsigned char[compressedBufferSize];
int result;
z_stream zipStream;
zipStream.zalloc = Z_NULL;
zipStream.zfree = Z_NULL;
zipStream.opaque = Z_NULL;
result = deflateInit2(&zipStream,
9, // level
Z_DEFLATED, // method
10, // windowBits
1, // memLevel
Z_DEFAULT_STRATEGY // strategy
);
if (result != Z_OK) exit(result);
zipStream.next_in = uncompressedData;
zipStream.avail_in = dataPos;
zipStream.next_out = compressedData;
zipStream.avail_out = compressedBufferSize;
result = deflate(&zipStream, Z_FINISH);
if (result != Z_STREAM_END) exit(result);
int compressedSize = compressedBufferSize-zipStream.avail_out;
PNGChunk *IDATChunk = new PNGChunk(compressedSize, "IDAT");
std::copy(compressedData, compressedData+compressedSize, IDATChunk->Data);
chunks.push_back(IDATChunk);
deflateEnd(&zipStream);
delete[] compressedData;
delete[] uncompressedData;
PNGChunk *IENDChunk = new PNGChunk(0, "IEND");
chunks.push_back(IENDChunk);
//Write chunks to output buffer
int finalDataSize = 8;
for(std::vector<PNGChunk*>::iterator iter = chunks.begin(), end = chunks.end(); iter != end; ++iter)
{
PNGChunk * cChunk = *iter;
finalDataSize += 4 + 4 + 4;
finalDataSize += cChunk->Length;
}
unsigned char * finalData = new unsigned char[finalDataSize];
int finalDataPos = 0;
//PNG File header
finalData[finalDataPos++] = 0x89;
finalData[finalDataPos++] = 0x50;
finalData[finalDataPos++] = 0x4E;
finalData[finalDataPos++] = 0x47;
finalData[finalDataPos++] = 0x0D;
finalData[finalDataPos++] = 0x0A;
finalData[finalDataPos++] = 0x1A;
finalData[finalDataPos++] = 0x0A;
for(std::vector<PNGChunk*>::iterator iter = chunks.begin(), end = chunks.end(); iter != end; ++iter)
{
PNGChunk * cChunk = *iter;
//Chunk length
finalData[finalDataPos++] = (cChunk->Length>>24)&0xFF;
finalData[finalDataPos++] = (cChunk->Length>>16)&0xFF;
finalData[finalDataPos++] = (cChunk->Length>>8)&0xFF;
finalData[finalDataPos++] = (cChunk->Length)&0xFF;
//Chunk name
std::copy(cChunk->Name, cChunk->Name+4, finalData+finalDataPos);
finalDataPos += 4;
//Chunk data
if(cChunk->Data)
{
std::copy(cChunk->Data, cChunk->Data+cChunk->Length, finalData+finalDataPos);
finalDataPos += cChunk->Length;
}
//Chunk CRC
unsigned long tempCRC = cChunk->CRC();
finalData[finalDataPos++] = (tempCRC>>24)&0xFF;
finalData[finalDataPos++] = (tempCRC>>16)&0xFF;
finalData[finalDataPos++] = (tempCRC>>8)&0xFF;
finalData[finalDataPos++] = (tempCRC)&0xFF;
delete cChunk;
}
std::vector<char> outputData(finalData, finalData+finalDataPos);
delete[] finalData;
return outputData;
}
//CRC functions, copypasta from W3 PNG spec.
/* Table of CRCs of all 8-bit messages. */
unsigned long crc_table[256];
/* Flag: has the table been computed? Initially false. */
int crc_table_computed = 0;
/* Make the table for a fast CRC. */
void make_crc_table(void)
{
unsigned long c;
int n, k;
for (n = 0; n < 256; n++) {
c = (unsigned long) n;
for (k = 0; k < 8; k++) {
if (c & 1)
c = 0xedb88320L ^ (c >> 1);
else
c = c >> 1;
}
crc_table[n] = c;
}
crc_table_computed = 1;
}
/* Update a running CRC with the bytes buf[0..len-1]--the CRC
should be initialized to all 1's, and the transmitted value
is the 1's complement of the final running CRC (see the
crc() routine below)). */
unsigned long update_crc(unsigned long crc, unsigned char *buf, int len)
{
unsigned long c = crc;
int n;
if (!crc_table_computed)
make_crc_table();
for (n = 0; n < len; n++)
{
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}
unsigned long format::CalculateCRC(unsigned char * data, int len)
{
return update_crc(0xffffffffL, data, len) ^ 0xffffffffL;
}
const static char hex[] = "0123456789ABCDEF";
ByteString format::URLEncode(ByteString source)
{

View File

@ -8,17 +8,10 @@ class VideoBuffer;
namespace format
{
const static char hex[] = "0123456789ABCDEF";
ByteString URLEncode(ByteString value);
ByteString URLDecode(ByteString value);
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> VideoBufferToPNG(const VideoBuffer & vidBuf);
std::vector<char> VideoBufferToBMP(const VideoBuffer & vidBuf);
std::vector<char> VideoBufferToPPM(const VideoBuffer & vidBuf);
std::vector<char> VideoBufferToPTI(const VideoBuffer & vidBuf);
VideoBuffer * PTIToVideoBuffer(std::vector<char> & data);
unsigned long CalculateCRC(unsigned char * data, int length);
}

View File

@ -15,7 +15,7 @@
#include <iostream>
#if defined(LIN)
#include "icon.h"
# include "powder-128.png.h"
#endif
#include <stdexcept>
@ -157,9 +157,14 @@ int SDLOpen()
SendMessage(WindowHandle, WM_SETICON, ICON_BIG, (LPARAM)hIconBig);
#endif
#ifdef LIN
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom((void*)app_icon, 128, 128, 32, 512, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
std::vector<pixel> imageData;
int imgw, imgh;
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_png), icon_png_size, false))
{
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
}
#endif
return 0;
@ -289,11 +294,11 @@ void EventProcess(SDL_Event event)
break;
case SDL_MOUSEWHEEL:
{
int x = event.wheel.x;
// int x = event.wheel.x;
int y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
{
x *= -1;
// x *= -1;
y *= -1;
}

View File

@ -22,64 +22,40 @@ int GetModifiers() { return 0; }
void SetCursorEnabled(int enabled) {}
unsigned int GetTicks() { return 0; }
void readFile(ByteString filename, std::vector<char> & storage)
static bool ReadFile(std::vector<char> &fileData, ByteString filename)
{
std::ifstream fileStream;
fileStream.open(filename.c_str(), std::ios::binary);
if(fileStream.is_open())
std::ifstream f(filename, std::ios::binary);
if (f) f.seekg(0, std::ios::end);
if (f) fileData.resize(f.tellg());
if (f) f.seekg(0);
if (f) f.read(&fileData[0], fileData.size());
if (!f)
{
fileStream.seekg(0, std::ios::end);
size_t fileSize = fileStream.tellg();
fileStream.seekg(0);
unsigned char * tempData = new unsigned char[fileSize];
fileStream.read((char *)tempData, fileSize);
fileStream.close();
std::vector<unsigned char> fileData;
storage.clear();
storage.insert(storage.end(), tempData, tempData+fileSize);
delete[] tempData;
}
}
void writeFile(ByteString filename, std::vector<char> & fileData)
{
std::ofstream fileStream;
fileStream.open(filename.c_str(), std::ios::binary);
if(fileStream.is_open())
{
fileStream.write(&fileData[0], fileData.size());
fileStream.close();
std::cerr << "ReadFile: " << filename << ": " << strerror(errno) << std::endl;
return false;
}
return true;
}
int main(int argc, char *argv[])
{
ByteString outputPrefix, inputFilename;
std::vector<char> inputFile;
ByteString ppmFilename, ptiFilename, ptiSmallFilename, pngFilename, pngSmallFilename;
std::vector<char> ppmFile, ptiFile, ptiSmallFile, pngFile, pngSmallFile;
if (!argv[1] || !argv[2]) {
std::cout << "Usage: " << argv[0] << " <inputFilename> <outputPrefix>" << std::endl;
return 1;
}
inputFilename = argv[1];
outputPrefix = argv[2];
auto inputFilename = ByteString(argv[1]);
auto outputFilename = ByteString(argv[2]) + ".png";
ppmFilename = outputPrefix+".ppm";
ptiFilename = outputPrefix+".pti";
ptiSmallFilename = outputPrefix+"-small.pti";
pngFilename = outputPrefix+".png";
pngSmallFilename = outputPrefix+"-small.png";
readFile(inputFilename, inputFile);
std::vector<char> fileData;
if (!ReadFile(fileData, inputFilename))
{
return 1;
}
GameSave * gameSave = NULL;
try
{
gameSave = new GameSave(inputFile);
gameSave = new GameSave(fileData);
}
catch (ParseException &e)
{
@ -119,19 +95,5 @@ int main(int argc, char *argv[])
ren->RenderEnd();
VideoBuffer screenBuffer = ren->DumpFrame();
//ppmFile = format::VideoBufferToPPM(screenBuffer);
ptiFile = format::VideoBufferToPTI(screenBuffer);
pngFile = format::VideoBufferToPNG(screenBuffer);
screenBuffer.Resize(1.0f/3.0f, true);
ptiSmallFile = format::VideoBufferToPTI(screenBuffer);
pngSmallFile = format::VideoBufferToPNG(screenBuffer);
//writeFile(ppmFilename, ppmFile);
writeFile(ptiFilename, ptiFile);
writeFile(ptiSmallFilename, ptiSmallFile);
writeFile(pngFilename, pngFile);
writeFile(pngSmallFilename, pngSmallFile);
screenBuffer.WritePNG(outputFilename);
}

View File

@ -17,9 +17,8 @@
#endif
#include <iostream>
#include "Config.h"
#if defined(LIN)
#include "icon.h"
# include "powder-128.png.h"
#endif
#include <csignal>
#include <stdexcept>
@ -214,9 +213,14 @@ void SDLOpen()
SendMessage(WindowHandle, WM_SETICON, ICON_BIG, (LPARAM)hIconBig);
#endif
#ifdef LIN
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom((void*)app_icon, 128, 128, 32, 512, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
std::vector<pixel> imageData;
int imgw, imgh;
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_png), icon_png_size, false))
{
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
}
#endif
}

View File

@ -1,16 +0,0 @@
#include "AvatarRequest.h"
#include "Config.h"
namespace http
{
AvatarRequest::AvatarRequest(ByteString username, int width, int height) :
ImageRequest(ByteString::Build(STATICSCHEME STATICSERVER "/avatars/", username, ".pti"), width, height)
{
}
AvatarRequest::~AvatarRequest()
{
}
}

View File

@ -1,17 +0,0 @@
#ifndef AVATARREQUEST2_H
#define AVATARREQUEST2_H
#include "ImageRequest.h"
namespace http
{
class AvatarRequest : public ImageRequest
{
public:
AvatarRequest(ByteString username, int width, int height);
virtual ~AvatarRequest();
};
}
#endif // AVATARREQUEST2_H

View File

@ -4,6 +4,8 @@
#include "graphics/Graphics.h"
#include "Config.h"
#include <iostream>
namespace http
{
ImageRequest::ImageRequest(ByteString url, int width, int height) :
@ -29,11 +31,10 @@ namespace http
if (data.size())
{
int imgw, imgh;
pixel *imageData = Graphics::ptif_unpack(&data[0], data.size(), &imgw, &imgh);
if (imageData)
std::vector<pixel> imageData;
if (PngDataToPixels(imageData, imgw, imgh, data.data(), data.size(), true))
{
vb = std::unique_ptr<VideoBuffer>(new VideoBuffer(imageData, imgw, imgh));
free(imageData);
vb = std::unique_ptr<VideoBuffer>(new VideoBuffer(imageData.data(), imgw, imgh));
}
else
{

View File

@ -5,7 +5,8 @@
#include <iostream>
const int curl_multi_wait_timeout_ms = 100;
const long curl_max_host_connections = 6;
const long curl_max_host_connections = 1;
const long curl_max_concurrent_streams = 1;
namespace http
{
@ -42,6 +43,9 @@ namespace http
if (multi)
{
curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, curl_max_host_connections);
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 67, 0)
curl_multi_setopt(multi, CURLMOPT_MAX_CONCURRENT_STREAMS, curl_max_concurrent_streams);
#endif
}
proxy = newProxy;

View File

@ -7,8 +7,8 @@ namespace http
ThumbnailRequest::ThumbnailRequest(int saveID, int saveDate, int width, int height) :
ImageRequest((
saveDate
? ByteString::Build(STATICSCHEME STATICSERVER "/", saveID, "_", saveDate, "_small.pti")
: ByteString::Build(STATICSCHEME STATICSERVER "/", saveID, "_small.pti")
? ByteString::Build(STATICSCHEME STATICSERVER "/", saveID, "_", saveDate, "_small.png")
: ByteString::Build(STATICSCHEME STATICSERVER "/", saveID, "_small.png")
), width, height)
{
}

View File

@ -1,6 +1,5 @@
client_files += files(
'APIRequest.cpp',
'AvatarRequest.cpp',
'GetUserInfoRequest.cpp',
'ImageRequest.cpp',
'Request.cpp',

View File

@ -6,12 +6,15 @@
#include <cstring>
#include <bzlib.h>
#include "common/Platform.h"
#include "FontReader.h"
#ifdef HIGH_QUALITY_RESAMPLE
#include "resampler/resampler.h"
#endif
#include <png.h>
VideoBuffer::VideoBuffer(int width, int height):
Width(width),
Height(height)
@ -186,115 +189,6 @@ char * Graphics::GenerateGradient(pixel * colours, float * points, int pointcoun
return newdata;
}
void *Graphics::ptif_pack(pixel *src, int w, int h, int *result_size){
int i = 0, datalen = (w*h)*3, cx = 0, cy = 0;
unsigned char *red_chan = (unsigned char*)calloc(1, w*h);
unsigned char *green_chan = (unsigned char*)calloc(1, w*h);
unsigned char *blue_chan = (unsigned char*)calloc(1, w*h);
unsigned char *data = (unsigned char*)malloc(((w*h)*3)+8);
unsigned char *result = (unsigned char*)malloc(((w*h)*3)+8);
for(cx = 0; cx<w; cx++){
for(cy = 0; cy<h; cy++){
red_chan[w*(cy)+(cx)] = PIXR(src[w*(cy)+(cx)]);
green_chan[w*(cy)+(cx)] = PIXG(src[w*(cy)+(cx)]);
blue_chan[w*(cy)+(cx)] = PIXB(src[w*(cy)+(cx)]);
}
}
memcpy(data, red_chan, w*h);
memcpy(data+(w*h), green_chan, w*h);
memcpy(data+((w*h)*2), blue_chan, w*h);
free(red_chan);
free(green_chan);
free(blue_chan);
result[0] = 'P';
result[1] = 'T';
result[2] = 'i';
result[3] = 1;
result[4] = w;
result[5] = w>>8;
result[6] = h;
result[7] = h>>8;
i -= 8;
if(BZ2_bzBuffToBuffCompress((char *)(result+8), (unsigned *)&i, (char *)data, datalen, 9, 0, 0) != 0){
free(data);
free(result);
return NULL;
}
*result_size = i+8;
free(data);
return result;
}
pixel *Graphics::ptif_unpack(void *datain, int size, int *w, int *h){
int width, height, i, cx, cy, resCode;
unsigned char *red_chan;
unsigned char *green_chan;
unsigned char *blue_chan;
unsigned char *data = (unsigned char*)datain;
unsigned char *undata;
pixel *result;
if(size<16){
printf("Image empty\n");
return NULL;
}
if(!(data[0]=='P' && data[1]=='T' && data[2]=='i')){
printf("Image header invalid\n");
return NULL;
}
width = data[4]|(data[5]<<8);
height = data[6]|(data[7]<<8);
i = (width*height)*3;
undata = (unsigned char*)calloc(1, (width*height)*3);
red_chan = (unsigned char*)calloc(1, width*height);
green_chan = (unsigned char*)calloc(1, width*height);
blue_chan = (unsigned char *)calloc(1, width*height);
result = (pixel *)calloc(width*height, PIXELSIZE);
resCode = BZ2_bzBuffToBuffDecompress((char *)undata, (unsigned *)&i, (char *)(data+8), size-8, 0, 0);
if (resCode){
printf("Decompression failure, %d\n", resCode);
free(red_chan);
free(green_chan);
free(blue_chan);
free(undata);
free(result);
return NULL;
}
if(i != (width*height)*3){
printf("Result buffer size mismatch, %d != %d\n", i, (width*height)*3);
free(red_chan);
free(green_chan);
free(blue_chan);
free(undata);
free(result);
return NULL;
}
memcpy(red_chan, undata, width*height);
memcpy(green_chan, undata+(width*height), width*height);
memcpy(blue_chan, undata+((width*height)*2), width*height);
for(cx = 0; cx<width; cx++){
for(cy = 0; cy<height; cy++){
result[width*(cy)+(cx)] = PIXRGB(red_chan[width*(cy)+(cx)], green_chan[width*(cy)+(cx)], blue_chan[width*(cy)+(cx)]);
}
}
*w = width;
*h = height;
free(red_chan);
free(green_chan);
free(blue_chan);
free(undata);
return result;
}
pixel *Graphics::resample_img_nn(pixel * src, int sw, int sh, int rw, int rh)
{
int y, x;
@ -1101,3 +995,151 @@ void Graphics::SetClipRect(int &x, int &y, int &w, int &h)
clipx2 = newX + newW;
clipy2 = newY + newH;
}
bool VideoBuffer::WritePNG(const ByteString &path) const
{
std::vector<png_const_bytep> rowPointers(Height);
for (auto y = 0; y < Height; ++y)
{
rowPointers[y] = (png_const_bytep)&Buffer[y * Width];
}
#ifdef WIN
FILE *f = _wfopen(Platform::WinWiden(path).c_str(), L"wb");
#else
FILE *f = fopen(path.c_str(), "wb");
#endif
if (!f)
{
std::cerr << "WritePNG: fopen failed" << std::endl;
return false;
}
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;
fclose(f);
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);
fclose(f);
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);
fclose(f);
return false;
}
png_init_io(png, f);
png_set_IHDR(png, info, Width, Height, 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);
fclose(f);
return true;
}
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;
}

View File

@ -66,6 +66,7 @@ public:
~VideoBuffer();
void CopyData(pixel * buffer, int width, int height, int pitch);
bool WritePNG(const ByteString &path) const;
};
class Graphics
@ -83,8 +84,6 @@ public:
static char * GenerateGradient(pixel * colours, float * points, int pointcount, int size);
//PTIF methods
static void *ptif_pack(pixel *src, int w, int h, int *result_size);
static pixel *ptif_unpack(void *datain, int size, int *w, int *h);
static pixel *resample_img_nn(pixel *src, int sw, int sh, int rw, int rh);
static pixel *resample_img(pixel *src, int sw, int sh, int rw, int rh);
static pixel *rescale_img(pixel *src, int sw, int sh, int *qw, int *qh, int f);
@ -137,4 +136,6 @@ public:
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);
#endif

View File

@ -36,6 +36,7 @@
#include "gui/interface/Engine.h"
#include <cstring>
#include <iostream>
#ifdef GetUserName
# undef GetUserName // dammit windows
@ -898,50 +899,68 @@ void GameView::NotifyBrushChanged(GameModel * sender)
ByteString GameView::TakeScreenshot(int captureUI, int fileType)
{
VideoBuffer *screenshot;
std::vector<char> data;
time_t screenshotTime = time(nullptr);
std::string extension;
std::unique_ptr<VideoBuffer> screenshot;
if (captureUI)
screenshot = new VideoBuffer(ren->DumpFrame());
{
screenshot = std::make_unique<VideoBuffer>(ren->DumpFrame());
}
else
screenshot = new VideoBuffer(ui::Engine::Ref().g->DumpFrame());
{
screenshot = std::make_unique<VideoBuffer>(ui::Engine::Ref().g->DumpFrame());
}
ByteString filename;
{
// Optional suffix to distinguish screenshots taken at the exact same time
ByteString suffix = "";
time_t screenshotTime = time(nullptr);
if (screenshotTime == lastScreenshotTime)
{
screenshotIndex++;
suffix = ByteString::Build(" (", screenshotIndex, ")");
}
else
{
screenshotIndex = 1;
}
lastScreenshotTime = screenshotTime;
std::string date = format::UnixtimeToDate(screenshotTime, "%Y-%m-%d %H.%M.%S");
filename = ByteString::Build("screenshot ", date, suffix);
}
if (fileType == 1)
{
data = format::VideoBufferToBMP(screenshot);
extension = ".bmp";
filename += ".bmp";
// We should be able to simply use SDL_PIXELFORMAT_XRGB8888 here with a bit depth of 32 to convert RGBA data to RGB data,
// and save the resulting surface directly. However, ubuntu-18.04 ships SDL2 so old that it doesn't have
// SDL_PIXELFORMAT_XRGB8888, so we first create an RGBA surface and then convert it.
auto *rgbaSurface = SDL_CreateRGBSurfaceWithFormatFrom(screenshot->Buffer, screenshot->Width, screenshot->Height, 32, screenshot->Width * sizeof(pixel), SDL_PIXELFORMAT_ARGB8888);
auto *rgbSurface = SDL_ConvertSurfaceFormat(rgbaSurface, SDL_PIXELFORMAT_RGB888, 0);
if (!rgbSurface || SDL_SaveBMP(rgbSurface, filename.c_str()))
{
std::cerr << "SDL_SaveBMP failed: " << SDL_GetError() << std::endl;
filename = "";
}
SDL_FreeSurface(rgbSurface);
SDL_FreeSurface(rgbaSurface);
}
else if (fileType == 2)
{
data = format::VideoBufferToPPM(screenshot);
extension = ".ppm";
filename += ".ppm";
if (!Platform::WriteFile(format::VideoBufferToPPM(*screenshot), filename))
{
filename = "";
}
}
else
{
data = format::VideoBufferToPNG(screenshot);
extension = ".png";
filename += ".png";
if (!screenshot->WritePNG(filename))
{
filename = "";
}
}
// Optional suffix to distinguish screenshots taken at the exact same time
ByteString suffix = "";
if (screenshotTime == lastScreenshotTime)
{
screenshotIndex++;
suffix = ByteString::Build(" (", screenshotIndex, ")");
}
else
{
screenshotIndex = 1;
}
std::string date = format::UnixtimeToDate(screenshotTime, "%Y-%m-%d %H.%M.%S");
ByteString filename = ByteString::Build("screenshot ", date, suffix, extension);
Platform::WriteFile(data, filename);
doScreenshot = false;
lastScreenshotTime = screenshotTime;
return filename;
}
@ -2155,6 +2174,7 @@ void GameView::OnDraw()
if (doScreenshot)
{
doScreenshot = false;
TakeScreenshot(0, 0);
}

View File

@ -26,10 +26,12 @@ void AvatarButton::OnResponse(std::unique_ptr<VideoBuffer> Avatar)
void AvatarButton::Tick(float dt)
{
if(!avatar && !tried && name.size() > 0)
if(!avatar && !tried)
{
tried = true;
RequestSetup(name, Size.X, Size.Y);
// TODO: Currently a very expensive-to-call endpoint; skips the cache server. If we call it too quickly, we get banned as
// an anti-DoS measure. This is why concurrent connections / connection multiplexing have been temporarily disabled.
RequestSetup(SCHEME SERVER "/Mini/GetAvatarPng.php?Username=" + name, Size.X, Size.Y);
RequestStart();
}

View File

@ -6,7 +6,7 @@
#include "Component.h"
#include "graphics/Graphics.h"
#include "gui/interface/Colour.h"
#include "client/http/AvatarRequest.h"
#include "client/http/ImageRequest.h"
#include "client/http/RequestMonitor.h"
#include <memory>
@ -14,7 +14,7 @@
namespace ui
{
class AvatarButton : public Component, public http::RequestMonitor<http::AvatarRequest>
class AvatarButton : public Component, public http::RequestMonitor<http::ImageRequest>
{
std::unique_ptr<VideoBuffer> avatar;
ByteString name;

View File

@ -207,7 +207,7 @@ bool PreviewModel::ParseSaveInfo(ByteString &saveInfoResponse)
{
delete saveInfo;
try
try // how does this differ from Client::GetSave?
{
std::istringstream dataStream(saveInfoResponse);
Json::Value objDocument;

View File

@ -1323,8 +1323,12 @@ int luatpt_screenshot(lua_State* l)
int fileType = luaL_optint(l, 2, 0);
ByteString filename = luacon_controller->TakeScreenshot(captureUI, fileType);
tpt_lua_pushByteString(l, filename);
return 1;
if (filename.size())
{
tpt_lua_pushByteString(l, filename);
return 1;
}
return 0;
}
int luatpt_record(lua_State* l)