123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- #include "SDLImage.hh"
- #include "PNG.hh"
- #include "SDLOutputSurface.hh"
- #include "checked_cast.hh"
- #include <cassert>
- #include <cstdlib>
- #include <cmath>
- #include <SDL.h>
- using std::string;
- using namespace gl;
- namespace openmsx {
- static SDLSurfacePtr getTempSurface(ivec2 size_)
- {
- int displayIndex = 0;
- SDL_DisplayMode currentMode;
- if (SDL_GetCurrentDisplayMode(displayIndex, ¤tMode) != 0) {
- // Error. Can this happen? Anything we can do?
- assert(false);
- }
- int bpp;
- Uint32 rmask, gmask, bmask, amask;
- SDL_PixelFormatEnumToMasks(
- currentMode.format, &bpp, &rmask, &gmask, &bmask, &amask);
- if (bpp == 32) {
- if (amask == 0) {
- amask = ~(rmask | gmask | bmask);
- }
- } else { // TODO should we also check {R,G,B}_loss == 0?
- // Use ARGB8888 as a fallback
- amask = 0xff000000;
- rmask = 0x00ff0000;
- gmask = 0x0000ff00;
- bmask = 0x000000ff;
- }
- return SDLSurfacePtr(abs(size_[0]), abs(size_[1]), 32,
- rmask, gmask, bmask, amask);
- }
- // Helper functions to draw a gradient
- // Extract R,G,B,A components to 8.16 bit fixed point.
- // Note the order R,G,B,A is arbitrary, the actual pixel value may have the
- // components in a different order.
- static void unpackRGBA(unsigned rgba,
- unsigned& r, unsigned&g, unsigned&b, unsigned& a)
- {
- r = (((rgba >> 24) & 0xFF) << 16) + 0x8000;
- g = (((rgba >> 16) & 0xFF) << 16) + 0x8000;
- b = (((rgba >> 8) & 0xFF) << 16) + 0x8000;
- a = (((rgba >> 0) & 0xFF) << 16) + 0x8000;
- }
- // Setup outer loop (vertical) interpolation parameters.
- // For each component there is a pair of (initial,delta) values. These values
- // are 8.16 bit fixed point, delta is signed.
- static void setupInterp1(unsigned rgba0, unsigned rgba1, unsigned length,
- unsigned& r0, unsigned& g0, unsigned& b0, unsigned& a0,
- int& dr, int& dg, int& db, int& da)
- {
- unpackRGBA(rgba0, r0, g0, b0, a0);
- if (length == 1) {
- dr = dg = db = da = 0;
- } else {
- unsigned r1, g1, b1, a1;
- unpackRGBA(rgba1, r1, g1, b1, a1);
- dr = int(r1 - r0) / int(length - 1);
- dg = int(g1 - g0) / int(length - 1);
- db = int(b1 - b0) / int(length - 1);
- da = int(a1 - a0) / int(length - 1);
- }
- }
- // Setup inner loop (horizontal) interpolation parameters.
- // - Like above we also output a pair of (initial,delta) values for each
- // component. But we pack two components in one 32-bit value. This leaves only
- // 16 bits per component, so now the values are 8.8 bit fixed point.
- // - To avoid carry/borrow from the lower to the upper pack, we make the lower
- // component always a positive number and output a boolean to indicate whether
- // we should add or subtract the delta from the initial value.
- // - The 8.8 fixed point calculations in the inner loop are less accurate than
- // the 8.16 calculations in the outer loop. This could result in not 100%
- // accurate gradients. Though only on very wide images and the error is
- // so small that it will hardly be visible (if at all).
- // - Packing 2 components in one value is not beneficial in the outer loop
- // because in this routine we need the individual components of the values
- // that are calculated by setupInterp1(). (It would also make the code even
- // more complex).
- static void setupInterp2(unsigned r0, unsigned g0, unsigned b0, unsigned a0,
- unsigned r1, unsigned g1, unsigned b1, unsigned a1,
- unsigned length,
- unsigned& rb, unsigned& ga,
- unsigned& drb, unsigned& dga,
- bool& subRB, bool& subGA)
- {
- // Pack the initial values for the components R,B and G,A into
- // a vector-type: two 8.16 scalars -> one [8.8 ; 8.8] vector
- rb = ((r0 << 8) & 0xffff0000) |
- ((b0 >> 8) & 0x0000ffff);
- ga = ((g0 << 8) & 0xffff0000) |
- ((a0 >> 8) & 0x0000ffff);
- subRB = subGA = false;
- if (length == 1) {
- drb = dga = 0;
- } else {
- // calculate delta values
- int dr = int(r1 - r0) / int(length - 1);
- int dg = int(g1 - g0) / int(length - 1);
- int db = int(b1 - b0) / int(length - 1);
- int da = int(a1 - a0) / int(length - 1);
- if (db < 0) { // make sure db is positive
- dr = -dr;
- db = -db;
- subRB = true;
- }
- if (da < 0) { // make sure da is positive
- dg = -dg;
- da = -da;
- subGA = true;
- }
- // also pack two 8.16 delta values in one [8.8 ; 8.8] vector
- drb = ((unsigned(dr) << 8) & 0xffff0000) |
- ((unsigned(db) >> 8) & 0x0000ffff);
- dga = ((unsigned(dg) << 8) & 0xffff0000) |
- ((unsigned(da) >> 8) & 0x0000ffff);
- }
- }
- // Pack two [8.8 ; 8.8] vectors into one pixel.
- static unsigned packRGBA(unsigned rb, unsigned ga)
- {
- return (rb & 0xff00ff00) | ((ga & 0xff00ff00) >> 8);
- }
- // Draw a gradient on the given surface. This is a bilinear interpolation
- // between 4 RGBA colors. One color for each corner, in this order:
- // 0 -- 1
- // | |
- // 2 -- 3
- static void gradient(const unsigned* rgba, SDL_Surface& surface, unsigned borderSize)
- {
- int width = surface.w - 2 * borderSize;
- int height = surface.h - 2 * borderSize;
- if ((width <= 0) || (height <= 0)) return;
- unsigned r0, g0, b0, a0;
- unsigned r1, g1, b1, a1;
- int dr02, dg02, db02, da02;
- int dr13, dg13, db13, da13;
- setupInterp1(rgba[0], rgba[2], height, r0, g0, b0, a0, dr02, dg02, db02, da02);
- setupInterp1(rgba[1], rgba[3], height, r1, g1, b1, a1, dr13, dg13, db13, da13);
- auto buffer = static_cast<unsigned*>(surface.pixels);
- buffer += borderSize;
- buffer += borderSize * (surface.pitch / sizeof(unsigned));
- for (int y = 0; y < height; ++y) {
- unsigned rb, ga;
- unsigned drb, dga;
- bool subRB, subGA;
- setupInterp2(r0, g0, b0, a0, r1, g1, b1, a1, width,
- rb, ga, drb, dga, subRB, subGA);
- // Depending on the subRB/subGA booleans, we need to add or
- // subtract the delta to/from the initial value. There are
- // 2 booleans so 4 combinations:
- if (!subRB) {
- if (!subGA) {
- for (int x = 0; x < width; ++x) {
- buffer[x] = packRGBA(rb, ga);
- rb += drb; ga += dga;
- }
- } else {
- for (int x = 0; x < width; ++x) {
- buffer[x] = packRGBA(rb, ga);
- rb += drb; ga -= dga;
- }
- }
- } else {
- if (!subGA) {
- for (int x = 0; x < width; ++x) {
- buffer[x] = packRGBA(rb, ga);
- rb -= drb; ga += dga;
- }
- } else {
- for (int x = 0; x < width; ++x) {
- buffer[x] = packRGBA(rb, ga);
- rb -= drb; ga -= dga;
- }
- }
- }
- r0 += dr02; g0 += dg02; b0 += db02; a0 += da02;
- r1 += dr13; g1 += dg13; b1 += db13; a1 += da13;
- buffer += (surface.pitch / sizeof(unsigned));
- }
- }
- // class SDLImage
- SDLImage::SDLImage(OutputSurface& output, const string& filename)
- : texture(loadImage(output, filename))
- , flipX(false), flipY(false)
- {
- }
- // TODO get rid of this constructor
- // instead allow to draw the same SDLImage to different sizes
- SDLImage::SDLImage(OutputSurface& output, const std::string& filename, float scaleFactor)
- : texture(loadImage(output, filename))
- , flipX(scaleFactor < 0.0f), flipY(scaleFactor < 0.0f)
- {
- size = trunc(vec2(size) * std::abs(scaleFactor)); // scale image size
- }
- // TODO get rid of this constructor, see above
- SDLImage::SDLImage(OutputSurface& output, const string& filename, ivec2 size_)
- : texture(loadImage(output, filename))
- , flipX(size_[0] < 0), flipY(size_[1] < 0)
- {
- size = size_; // replace image size
- }
- SDLImage::SDLImage(OutputSurface& output, ivec2 size_, unsigned rgba)
- : flipX(size_[0] < 0), flipY(size_[1] < 0)
- {
- initSolid(output, size_, rgba, 0, 0); // no border
- }
- SDLImage::SDLImage(OutputSurface& output, ivec2 size_, const unsigned* rgba,
- unsigned borderSize, unsigned borderRGBA)
- : flipX(size_[0] < 0), flipY(size_[1] < 0)
- {
- if ((rgba[0] == rgba[1]) &&
- (rgba[0] == rgba[2]) &&
- (rgba[0] == rgba[3])) {
- initSolid (output, size_, rgba[0], borderSize, borderRGBA);
- } else {
- initGradient(output, size_, rgba, borderSize, borderRGBA);
- }
- }
- SDLImage::SDLImage(OutputSurface& output, SDLSurfacePtr image)
- : texture(toTexture(output, *image))
- , flipX(false), flipY(false)
- {
- }
- SDLTexturePtr SDLImage::toTexture(OutputSurface& output, SDL_Surface& surface)
- {
- SDLTexturePtr result(SDL_CreateTextureFromSurface(
- checked_cast<SDLOutputSurface&>(output).getSDLRenderer(), &surface));
- SDL_SetTextureBlendMode(result.get(), SDL_BLENDMODE_BLEND);
- SDL_QueryTexture(result.get(), nullptr, nullptr, &size[0], &size[1]);
- return result;
- }
- SDLTexturePtr SDLImage::loadImage(OutputSurface& output, const string& filename)
- {
- bool want32bpp = true;
- return toTexture(output, *PNG::load(filename, want32bpp));
- }
- static unsigned convertColor(const SDL_PixelFormat& format, unsigned rgba)
- {
- return SDL_MapRGBA(
- &format,
- (rgba >> 24) & 0xff,
- (rgba >> 16) & 0xff,
- (rgba >> 8) & 0xff,
- (rgba >> 0) & 0xff);
- }
- static void drawBorder(SDL_Surface& image, int size, unsigned rgba)
- {
- if (size <= 0) return;
- unsigned color = convertColor(*image.format, rgba);
- bool onlyBorder = ((2 * size) >= image.w) ||
- ((2 * size) >= image.h);
- if (onlyBorder) {
- SDL_FillRect(&image, nullptr, color);
- } else {
- // +--------------------+
- // | 1 |
- // +---+------------+---+
- // | | | |
- // | 3 | | 4 |
- // | | | |
- // +---+------------+---+
- // | 2 |
- // +--------------------+
- SDL_Rect rect;
- rect.x = 0;
- rect.y = 0;
- rect.w = image.w;
- rect.h = size;
- SDL_FillRect(&image, &rect, color); // 1
- rect.y = image.h - size;
- SDL_FillRect(&image, &rect, color); // 2
- rect.y = size;
- rect.w = size;
- rect.h = image.h - 2 * size;
- SDL_FillRect(&image, &rect, color); // 3
- rect.x = image.w - size;
- SDL_FillRect(&image, &rect, color); // 4
- }
- }
- void SDLImage::initSolid(OutputSurface& output, ivec2 size_, unsigned rgba,
- unsigned borderSize, unsigned borderRGBA)
- {
- checkSize(size_);
- if ((size_[0] == 0) || (size_[1] == 0)) {
- // SDL_FillRect crashes on zero-width surfaces, so check for it
- return;
- }
- SDLSurfacePtr tmp32 = getTempSurface(size_);
- // draw interior
- SDL_FillRect(tmp32.get(), nullptr, convertColor(*tmp32->format, rgba));
- drawBorder(*tmp32, borderSize, borderRGBA);
- texture = toTexture(output, *tmp32);
- }
- void SDLImage::initGradient(OutputSurface& output, ivec2 size_, const unsigned* rgba_,
- unsigned borderSize, unsigned borderRGBA)
- {
- checkSize(size_);
- if ((size_[0] == 0) || (size_[1] == 0)) {
- return;
- }
- unsigned rgba[4];
- for (unsigned i = 0; i < 4; ++i) {
- rgba[i] = rgba_[i];
- }
- if (flipX) {
- std::swap(rgba[0], rgba[1]);
- std::swap(rgba[2], rgba[3]);
- }
- if (flipY) {
- std::swap(rgba[0], rgba[2]);
- std::swap(rgba[1], rgba[3]);
- }
- SDLSurfacePtr tmp32 = getTempSurface(size_);
- for (auto& c : rgba) {
- c = convertColor(*tmp32->format, c);
- }
- gradient(rgba, *tmp32, borderSize);
- drawBorder(*tmp32, borderSize, borderRGBA);
- texture = toTexture(output, *tmp32);
- }
- void SDLImage::draw(OutputSurface& output, gl::ivec2 pos, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha)
- {
- assert(r == 255); (void)r; // SDL2 supports this now, but do we need it?
- assert(g == 255); (void)g;
- assert(b == 255); (void)b;
- if (!texture) return;
- auto [x, y] = pos;
- auto [w, h] = size;
- if (flipX) x -= w;
- if (flipY) y -= h;
- auto renderer = checked_cast<SDLOutputSurface&>(output).getSDLRenderer();
- SDL_SetTextureAlphaMod(texture.get(), alpha);
- SDL_Rect dst = {x, y, w, h};
- SDL_RenderCopy(renderer, texture.get(), nullptr, &dst);
- }
- } // namespace openmsx
|