#include #include #include #include #include #include #include #include "common/platform/Platform.h" #include "FontReader.h" #include "Graphics.h" #include "resampler/resampler.h" #include "SimulationConfig.h" VideoBuffer::VideoBuffer(Vec2 size): video(size) {} VideoBuffer::VideoBuffer(pixel const *data, Vec2 size): VideoBuffer(size) { std::copy_n(data, size.X * size.Y, video.data()); } VideoBuffer::VideoBuffer(pixel const *data, Vec2 size, size_t rowStride): VideoBuffer(size) { for(int y = 0; y < size.Y; y++) std::copy_n(data + rowStride * y, size.X, video.RowIterator(Vec2(0, y))); } VideoBuffer::VideoBuffer(int width, int height): VideoBuffer(Vec2(width, height)) {} VideoBuffer::VideoBuffer(VideoBuffer * old): VideoBuffer(*old) {} VideoBuffer::VideoBuffer(pixel const *buffer, int width, int height, int pitch): VideoBuffer(buffer, Vec2(width, height), pitch == 0 ? width : pitch) {} void VideoBuffer::CopyData(pixel * buffer, int width, int height, int pitch) { for (auto y = 0; y < height; ++y) { std::copy(buffer + y * pitch, buffer + y * pitch + width, Buffer.data() + y * width); } } void VideoBuffer::Crop(Rect rect) { Crop(rect.Size().X, rect.Size().Y, rect.TopLeft.X, rect.TopLeft.Y); } void VideoBuffer::Crop(int width, int height, int x, int y) { CopyData(Buffer.data() + y * Width + x, width, height, Width); video.SetSize(Vec2(width, height)); } void VideoBuffer::Resize(float factor, bool resample) { int newWidth = int(Width * factor); int newHeight = int(Height * factor); Resize(newWidth, newHeight, resample); } void VideoBuffer::Resize(int width, int height, bool resample, bool fixedRatio) { int newWidth = width; int newHeight = height; pixel * newBuffer; if(newHeight == -1 && newWidth == -1) return; if(newHeight == -1 || newWidth == -1) { if(newHeight == -1) newHeight = int(float(Height) * newWidth / Width); if(newWidth == -1) newWidth = int(float(Width) * newHeight / Height); } else if(fixedRatio) { //Force proportions if(newWidth*Height > newHeight*Width) // same as nW/W > nH/H newWidth = (int)(Width * (newHeight/(float)Height)); else newHeight = (int)(Height * (newWidth/(float)Width)); } if(resample) newBuffer = Graphics::resample_img(Buffer.data(), Width, Height, newWidth, newHeight); else newBuffer = Graphics::resample_img_nn(Buffer.data(), Width, Height, newWidth, newHeight); if(newBuffer) { Buffer.assign(newBuffer, newBuffer + newWidth * newHeight); delete[] newBuffer; video.SetSize(Vec2(newWidth, newHeight)); } } int VideoBuffer::SetCharacter(int x, int y, String::value_type c, int r, int g, int b, int a) { FontReader reader(c); for (int j = -2; j < FONT_H - 2; j++) for (int i = 0; i < reader.GetWidth(); i++) SetPixel(x + i, y + j, r, g, b, reader.NextPixel() * a / 3); return x + reader.GetWidth(); } int VideoBuffer::BlendCharacter(int x, int y, String::value_type c, int r, int g, int b, int a) { FontReader reader(c); for (int j = -2; j < FONT_H - 2; j++) for (int i = 0; i < reader.GetWidth(); i++) BlendPixel(x + i, y + j, r, g, b, reader.NextPixel() * a / 3); return x + reader.GetWidth(); } int VideoBuffer::AddCharacter(int x, int y, String::value_type c, int r, int g, int b, int a) { FontReader reader(c); for (int j = -2; j < FONT_H - 2; j++) for (int i = 0; i < reader.GetWidth(); i++) AddPixel(x + i, y + j, r, g, b, reader.NextPixel() * a / 3); return x + reader.GetWidth(); } pixel *Graphics::resample_img_nn(pixel * src, int sw, int sh, int rw, int rh) { int y, x; pixel *q = NULL; q = new pixel[rw*rh]; for (y=0; yget_clist_x(), resamplers[0]->get_clist_y(), filter_scale, filter_scale); samples[i] = new float[sourceWidth]; } unsigned char * resultImage = new unsigned char[resultHeight * resultPitch]; std::fill(resultImage, resultImage + (resultHeight*resultPitch), 0); //Resample time int resultY = 0; for (int sourceY = 0; sourceY < sourceHeight; sourceY++) { unsigned char * sourcePixel = &source[sourceY * sourcePitch]; //Move pixel components into channel samples for (int c = 0; c < PIXELCHANNELS; c++) { for (int x = 0; x < sourceWidth; x++) { samples[c][x] = sourcePixel[(x*PIXELSIZE)+c] * (1.0f/255.0f); } } //Put channel sample data into resampler for (int c = 0; c < PIXELCHANNELS; c++) { if (!resamplers[c]->put_line(&samples[c][0])) { printf("Out of memory!\n"); return NULL; } } //Perform resample and Copy components from resampler result samples to image buffer for ( ; ; ) { int comp_index; for (comp_index = 0; comp_index < PIXELCHANNELS; comp_index++) { const float* resultSamples = resamplers[comp_index]->get_line(); if (!resultSamples) break; unsigned char * resultPixel = &resultImage[(resultY * resultPitch) + comp_index]; for (int x = 0; x < resultWidth; x++) { int c = (int)(255.0f * resultSamples[x] + .5f); if (c < 0) c = 0; else if (c > 255) c = 255; *resultPixel = (unsigned char)c; resultPixel += PIXELSIZE; } } if (comp_index < PIXELCHANNELS) break; resultY++; } } //Clean up for(int i = 0; i < PIXELCHANNELS; i++) { delete resamplers[i]; delete[] samples[i]; } return (pixel*)resultImage; #else if constexpr (DEBUG) { std::cout << "Resampling " << sw << "x" << sh << " to " << rw << "x" << rh << std::endl; } bool stairstep = false; if(rw < sw || rh < sh) { float fx = (float)(((float)sw)/((float)rw)); float fy = (float)(((float)sh)/((float)rh)); int fxint, fyint; double fxintp_t, fyintp_t; float fxf = modf(fx, &fxintp_t), fyf = modf(fy, &fyintp_t); fxint = fxintp_t; fyint = fyintp_t; if(((fxint & (fxint-1)) == 0 && fxf < 0.1f) || ((fyint & (fyint-1)) == 0 && fyf < 0.1f)) stairstep = true; if constexpr (DEBUG) { if(stairstep) std::cout << "Downsampling by " << fx << "x" << fy << " using stairstepping" << std::endl; else std::cout << "Downsampling by " << fx << "x" << fy << " without stairstepping" << std::endl; } } int y, x, fxceil, fyceil; //int i,j,x,y,w,h,r,g,b,c; pixel *q = NULL; if(rw == sw && rh == sh){ //Don't resample q = new pixel[rw*rh]; std::copy(src, src+(rw*rh), q); } else if(!stairstep) { float fx, fy, fyc, fxc; double intp; pixel tr, tl, br, bl; q = new pixel[rw*rh]; //Bilinear interpolation for upscaling for (y=0; y=sw) fxceil = sw-1; if (fyceil>=sh) fyceil = sh-1; tr = src[sw*(int)floor(fy)+fxceil]; tl = src[sw*(int)floor(fy)+(int)floor(fx)]; br = src[sw*fyceil+fxceil]; bl = src[sw*fyceil+(int)floor(fx)]; q[rw*y+x] = PIXRGB( (int)(((((float)PIXR(tl))*(1.0f-fxc))+(((float)PIXR(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXR(bl))*(1.0f-fxc))+(((float)PIXR(br))*(fxc)))*(fyc)), (int)(((((float)PIXG(tl))*(1.0f-fxc))+(((float)PIXG(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXG(bl))*(1.0f-fxc))+(((float)PIXG(br))*(fxc)))*(fyc)), (int)(((((float)PIXB(tl))*(1.0f-fxc))+(((float)PIXB(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXB(bl))*(1.0f-fxc))+(((float)PIXB(br))*(fxc)))*(fyc)) ); } } else { //Stairstepping float fx, fy, fyc, fxc; double intp; pixel tr, tl, br, bl; int rrw = rw, rrh = rh; pixel * oq; oq = new pixel[sw*sh]; std::copy(src, src+(sw*sh), oq); rw = sw; rh = sh; while(rrw != rw && rrh != rh){ if(rw > rrw) rw *= 0.7; if(rh > rrh) rh *= 0.7; if(rw <= rrw) rw = rrw; if(rh <= rrh) rh = rrh; q = new pixel[rw*rh]; //Bilinear interpolation for (y=0; y=sw) fxceil = sw-1; if (fyceil>=sh) fyceil = sh-1; tr = oq[sw*(int)floor(fy)+fxceil]; tl = oq[sw*(int)floor(fy)+(int)floor(fx)]; br = oq[sw*fyceil+fxceil]; bl = oq[sw*fyceil+(int)floor(fx)]; q[rw*y+x] = PIXRGB( (int)(((((float)PIXR(tl))*(1.0f-fxc))+(((float)PIXR(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXR(bl))*(1.0f-fxc))+(((float)PIXR(br))*(fxc)))*(fyc)), (int)(((((float)PIXG(tl))*(1.0f-fxc))+(((float)PIXG(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXG(bl))*(1.0f-fxc))+(((float)PIXG(br))*(fxc)))*(fyc)), (int)(((((float)PIXB(tl))*(1.0f-fxc))+(((float)PIXB(tr))*(fxc)))*(1.0f-fyc) + ((((float)PIXB(bl))*(1.0f-fxc))+(((float)PIXB(br))*(fxc)))*(fyc)) ); } delete[] oq; oq = q; sw = rw; sh = rh; } } return q; #endif } int Graphics::textwidth(const String &str) { int x = 0; for (size_t i = 0; i < str.length(); i++) { if (str[i] == '\b') { if (str.length() <= i+1) break; i++; continue; } else if (str[i] == '\x0F') { if (str.length() <= i+3) break; i += 3; continue; } x += FontReader(str[i]).GetWidth(); } return x-1; } int Graphics::CharWidth(String::value_type c) { return FontReader(c).GetWidth(); } int Graphics::textwidthx(const String &str, int w) { int x = 0,n = 0,cw = 0; for (size_t i = 0; i < str.length(); i++) { if (str[i] == '\b') { if (str.length() <= i+1) break; i++; continue; } else if (str[i] == '\x0F') { if (str.length() <= i+3) break; i += 3; continue; } cw = FontReader(str[i]).GetWidth(); if (x+(cw/2) >= w) break; x += cw; n++; } return n; } void Graphics::textsize(const String &str, int & width, int & height) { if(!str.size()) { width = 0; height = FONT_H-2; return; } int cHeight = FONT_H-2, cWidth = 0, lWidth = 0; for (size_t i = 0; i < str.length(); i++) { if (str[i] == '\n') { cWidth = 0; cHeight += FONT_H; } else if (str[i] == '\x0F') { if (str.length() <= i+3) break; i += 3; } else if (str[i] == '\b') { if (str.length() <= i+1) break; i++; } else { cWidth += FontReader(str[i]).GetWidth(); if(cWidth>lWidth) lWidth = cWidth; } } width = lWidth; height = cHeight; } void Graphics::draw_icon(int x, int y, Icon icon, unsigned char alpha, bool invert) { y--; switch(icon) { case IconOpen: if(invert) drawchar(x, y, 0xE001, 0, 0, 0, alpha); else drawchar(x, y, 0xE001, 255, 255, 255, alpha); break; case IconReload: if(invert) drawchar(x, y, 0xE011, 0, 0, 0, alpha); else drawchar(x, y, 0xE011, 255, 255, 255, alpha); break; case IconSave: if(invert) drawchar(x, y, 0xE002, 0, 0, 0, alpha); else drawchar(x, y, 0xE002, 255, 255, 255, alpha); break; case IconVoteUp: if(invert) { drawchar(x-11, y+1, 0xE04B, 0, 100, 0, alpha); drawtext(x+2, y+1, "Vote", 0, 100, 0, alpha); } else { drawchar(x-11, y+1, 0xE04B, 0, 187, 18, alpha); drawtext(x+2, y+1, "Vote", 0, 187, 18, alpha); } break; case IconVoteDown: if(invert) drawchar(x, y, 0xE04A, 100, 10, 0, alpha); else drawchar(x, y, 0xE04A, 187, 40, 0, alpha); break; case IconTag: if(invert) drawchar(x, y, 0xE003, 0, 0, 0, alpha); else drawchar(x, y, 0xE003, 255, 255, 255, alpha); break; case IconNew: if(invert) drawchar(x, y, 0xE012, 0, 0, 0, alpha); else drawchar(x, y, 0xE012, 255, 255, 255, alpha); break; case IconLogin: if(invert) drawchar(x, y+1, 0xE004, 0, 0, 0, alpha); else drawchar(x, y+1, 0xE004, 255, 255, 255, alpha); break; case IconSimulationSettings: if(invert) drawchar(x, y+1, 0xE04F, 0, 0, 0, alpha); else drawchar(x, y+1, 0xE04F, 255, 255, 255, alpha); break; case IconRenderSettings: if(invert) { drawchar(x, y+1, 0xE058, 255, 0, 0, alpha); drawchar(x, y+1, 0xE059, 0, 255, 0, alpha); drawchar(x, y+1, 0xE05A, 0, 0, 255, alpha); } else { addchar(x, y+1, 0xE058, 255, 0, 0, alpha); addchar(x, y+1, 0xE059, 0, 255, 0, alpha); addchar(x, y+1, 0xE05A, 0, 0, 255, alpha); } break; case IconPause: if(invert) drawchar(x, y, 0xE010, 0, 0, 0, alpha); else drawchar(x, y, 0xE010, 255, 255, 255, alpha); break; case IconFavourite: if(invert) drawchar(x, y, 0xE04C, 100, 80, 32, alpha); else drawchar(x, y, 0xE04C, 192, 160, 64, alpha); break; case IconReport: if(invert) drawchar(x, y, 0xE063, 140, 140, 0, alpha); else drawchar(x, y, 0xE063, 255, 255, 0, alpha); break; case IconUsername: if(invert) { drawchar(x, y, 0xE00B, 32, 64, 128, alpha); drawchar(x, y, 0xE00A, 0, 0, 0, alpha); } else { drawchar(x, y, 0xE00B, 32, 64, 128, alpha); drawchar(x, y, 0xE00A, 255, 255, 255, alpha); } break; case IconPassword: if(invert) { drawchar(x, y, 0xE00C, 160, 144, 32, alpha); drawchar(x, y, 0xE004, 0, 0, 0, alpha); } else { drawchar(x, y, 0xE00C, 160, 144, 32, alpha); drawchar(x, y, 0xE004, 255, 255, 255, alpha); } break; case IconClose: if(invert) drawchar(x, y, 0xE02A, 20, 20, 20, alpha); else drawchar(x, y, 0xE02A, 230, 230, 230, alpha); break; case IconVoteSort: if (invert) { drawchar(x, y, 0xE029, 44, 48, 32, alpha); drawchar(x, y, 0xE028, 32, 44, 32, alpha); drawchar(x, y, 0xE027, 128, 128, 128, alpha); } else { drawchar(x, y, 0xE029, 144, 48, 32, alpha); drawchar(x, y, 0xE028, 32, 144, 32, alpha); drawchar(x, y, 0xE027, 255, 255, 255, alpha); } break; case IconDateSort: if (invert) { drawchar(x, y, 0xE026, 32, 32, 32, alpha); } else { drawchar(x, y, 0xE026, 255, 255, 255, alpha); } break; case IconMyOwn: if (invert) { drawchar(x, y, 0xE014, 192, 160, 64, alpha); drawchar(x, y, 0xE013, 32, 32, 32, alpha); } else { drawchar(x, y, 0xE014, 192, 160, 64, alpha); drawchar(x, y, 0xE013, 255, 255, 255, alpha); } break; case IconSearch: drawchar(x, y, 0xE00E, 30, 30, 180, alpha); drawchar(x, y, 0xE00F, 255, 255, 255, alpha); break; case IconDelete: if(invert) { drawchar(x, y, 0xE006, 159, 47, 31, alpha); drawchar(x, y, 0xE005, 0, 0, 0, alpha); } else { drawchar(x, y, 0xE006, 159, 47, 31, alpha); drawchar(x, y, 0xE005, 255, 255, 255, alpha); } break; case IconAdd: if(invert) { drawchar(x, y, 0xE006, 32, 144, 32, alpha); drawchar(x, y, 0xE009, 0, 0, 0, alpha); } else { drawchar(x, y, 0xE006, 32, 144, 32, alpha); drawchar(x, y, 0xE009, 255, 255, 255, alpha); } break; case IconVelocity: drawchar(x+1, y, 0xE018, 128, 160, 255, alpha); break; case IconPressure: if(invert) drawchar(x+1, y+1, 0xE019, 180, 160, 16, alpha); else drawchar(x+1, y+1, 0xE019, 255, 212, 32, alpha); break; case IconPersistant: if(invert) drawchar(x+1, y+1, 0xE01A, 20, 20, 20, alpha); else drawchar(x+1, y+1, 0xE01A, 212, 212, 212, alpha); break; case IconFire: drawchar(x+1, y+1, 0xE01B, 255, 0, 0, alpha); drawchar(x+1, y+1, 0xE01C, 255, 255, 64, alpha); break; case IconBlob: if(invert) drawchar(x+1, y, 0xE03F, 55, 180, 55, alpha); else drawchar(x+1, y, 0xE03F, 55, 255, 55, alpha); break; case IconHeat: drawchar(x+3, y, 0xE03E, 255, 0, 0, alpha); if(invert) drawchar(x+3, y, 0xE03D, 0, 0, 0, alpha); else drawchar(x+3, y, 0xE03D, 255, 255, 255, alpha); break; case IconBlur: if(invert) drawchar(x+1, y, 0xE044, 50, 70, 180, alpha); else drawchar(x+1, y, 0xE044, 100, 150, 255, alpha); break; case IconGradient: if(invert) drawchar(x+1, y+1, 0xE053, 255, 50, 255, alpha); else drawchar(x+1, y+1, 0xE053, 205, 50, 205, alpha); break; case IconLife: if(invert) drawchar(x, y+1, 0xE060, 0, 0, 0, alpha); else drawchar(x, y+1, 0xE060, 255, 255, 255, alpha); break; case IconEffect: drawchar(x+1, y, 0xE061, 255, 255, 160, alpha); break; case IconGlow: drawchar(x+1, y, 0xE05F, 200, 255, 255, alpha); break; case IconWarp: drawchar(x+1, y, 0xE05E, 255, 255, 255, alpha); break; case IconBasic: if(invert) drawchar(x+1, y+1, 0xE05B, 50, 50, 0, alpha); else drawchar(x+1, y+1, 0xE05B, 255, 255, 200, alpha); break; case IconAltAir: if(invert) { drawchar(x+1, y+1, 0xE054, 180, 55, 55, alpha); drawchar(x+1, y+1, 0xE055, 55, 180, 55, alpha); } else { drawchar(x+1, y+1, 0xE054, 255, 55, 55, alpha); drawchar(x+1, y+1, 0xE055, 55, 255, 55, alpha); } break; default: if(invert) drawchar(x, y, 't', 0, 0, 0, alpha); else drawchar(x, y, 't', 255, 255, 255, alpha); break; } } 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(WINDOWW, WINDOWH); std::copy(vid, vid+(WINDOWW*WINDOWH), newBuffer.Buffer.data()); return newBuffer; } void Graphics::SetClipRect(int &x, int &y, int &w, int &h) { int newX = x; int newY = y; int newW = w; int newH = h; if (newX < 0) newX = 0; if (newY < 0) newY = 0; if (newW > WINDOWW - newX) newW = WINDOWW - newX; if (newH > WINDOWH - newY) newH = WINDOWH - newY; x = clipx1; y = clipy1; w = clipx2 - clipx1; h = clipy2 - clipy1; clipx1 = newX; clipy1 = newY; clipx2 = newX + newW; clipy2 = newY + newH; } bool VideoBuffer::WritePNG(const ByteString &path) const { std::vector rowPointers(Height); for (auto y = 0; y < Height; ++y) { rowPointers[y] = (png_const_bytep)&Buffer[y * Width]; } 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 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, 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); return Platform::WriteFile(imf.data, path); } bool PngDataToPixels(std::vector &imageData, int &imgw, int &imgh, const char *pngData, size_t pngDataSize, bool addBackground) { std::vector 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; } std::vector Graphics::Gradient(std::vector stops, int resolution) { std::vector table(resolution, 0); if (stops.size() >= 2) { std::sort(stops.begin(), stops.end()); auto stop = -1; for (auto i = 0; i < resolution; ++i) { auto point = i / (float)resolution; while (stop < (int)stops.size() - 1 && stops[stop + 1].point <= point) { ++stop; } if (stop < 0 || stop >= (int)stops.size() - 1) { continue; } auto &left = stops[stop]; auto &right = stops[stop + 1]; auto f = (point - left.point) / (right.point - left.point); table[i] = PIXRGB( int(int(PIXR(left.color)) + (int(PIXR(right.color)) - int(PIXR(left.color))) * f), int(int(PIXG(left.color)) + (int(PIXG(right.color)) - int(PIXG(left.color))) * f), int(int(PIXB(left.color)) + (int(PIXB(right.color)) - int(PIXB(left.color))) * f) ); } } return table; }