text: #include "Etterna/Globals/global.h"
  #include "RageSurface.h"
  #include "RageUtil/Utils/RageUtil.h"
  #include <stb/stb_image.h>
  #include <climits>
  
  int32_t
  RageSurfacePalette::FindColor(const RageSurfaceColor& color) const
  {
  	for (int i = 0; i < ncolors; ++i)
  		if (colors[i] == color)
  			return i;
  	return -1;
  }
  
  /* XXX: untested */
  int32_t
  RageSurfacePalette::FindClosestColor(const RageSurfaceColor& color) const
  {
  	int iBest = -1;
  	int iBestDist = INT_MAX;
  	for (int i = 0; i < ncolors; ++i) {
  		if (colors[i] == color)
  			return i;
  
  		int iDist = abs(colors[i].r - color.r) + abs(colors[i].g - color.g) +
  					abs(colors[i].b - color.b) + abs(colors[i].a - color.a);
  		if (iDist < iBestDist) {
  			iBestDist = iDist;
  			iBest = i;
  		}
  	}
  
  	return iBest;
  }
  
  RageSurfaceFormat::RageSurfaceFormat()
    : Rmask(Mask[0])
    , Gmask(Mask[1])
    , Bmask(Mask[2])
    , Amask(Mask[3])
    , Rshift(Shift[0])
    , Gshift(Shift[1])
    , Bshift(Shift[2])
    , Ashift(Shift[3])
  {
  	palette = NULL;
  }
  
  RageSurfaceFormat::RageSurfaceFormat(const RageSurfaceFormat& cpy)
    : Rmask(Mask[0])
    , Gmask(Mask[1])
    , Bmask(Mask[2])
    , Amask(Mask[3])
    , Rshift(Shift[0])
    , Gshift(Shift[1])
    , Bshift(Shift[2])
    , Ashift(Shift[3])
  {
  	memcpy(this, &cpy, sizeof(RageSurfaceFormat));
  	if (palette != nullptr)
  		palette = new RageSurfacePalette(*palette);
  }
  
  RageSurfaceFormat::~RageSurfaceFormat()
  {
  	delete palette;
  }
  
  void
  RageSurfaceFormat::GetRGB(uint32_t val,
  						  uint8_t* r,
  						  uint8_t* g,
  						  uint8_t* b) const
  {
  	if (BytesPerPixel == 1) {
  		ASSERT(palette != NULL);
  		*r = palette->colors[val].r;
  		*g = palette->colors[val].g;
  		*b = palette->colors[val].b;
  	} else {
  		*r = int8_t((val & Mask[0]) >> Shift[0] << Loss[0]);
  		*g = int8_t((val & Mask[1]) >> Shift[1] << Loss[1]);
  		*b = int8_t((val & Mask[2]) >> Shift[2] << Loss[2]);
  	}
  }
  
  bool
  RageSurfaceFormat::MapRGBA(uint8_t r,
  						   uint8_t g,
  						   uint8_t b,
  						   uint8_t a,
  						   uint32_t& val) const
  {
  	if (BytesPerPixel == 1) {
  		RageSurfaceColor c(r, g, b, a);
  		int32_t n = palette->FindColor(c);
  		if (n == -1)
  			return false;
  		val = (uint32_t)n;
  	} else {
  		val = (r >> Loss[0] << Shift[0]) | (g >> Loss[1] << Shift[1]) |
  			  (b >> Loss[2] << Shift[2]) | (a >> Loss[3] << Shift[3]);
  	}
  	return true;
  }
  
  bool
  RageSurfaceFormat::operator==(const RageSurfaceFormat& rhs) const
  {
  	if (!Equivalent(rhs))
  		return false;
  
  	if (BytesPerPixel == 1)
  		if (memcmp(palette, rhs.palette, sizeof(RageSurfaceFormat)))
  			return false;
  
  	return true;
  }
  
  bool
  RageSurfaceFormat::Equivalent(const RageSurfaceFormat& rhs) const
  {
  #define COMP(a)                                                                \
  	if ((a) != rhs.a)                                                          \
  		return false;
  	COMP(BytesPerPixel);
  	COMP(Rmask);
  	COMP(Gmask);
  	COMP(Bmask);
  	COMP(Amask);
  
  	return true;
  }
  
  RageSurface::RageSurface()
  {
  	pixels = nullptr;
  	pixels_owned = true;
  	stb_loadpoint = false;
  }
  
  RageSurface::RageSurface(const RageSurface& cpy)
  {
  	w = cpy.w;
  	h = cpy.h;
  	pitch = cpy.pitch;
  	flags = cpy.flags;
  	pixels_owned = true;
  	stb_loadpoint = cpy.stb_loadpoint;
  	if (cpy.pixels) {
  		pixels = new uint8_t[pitch * h];
  		memcpy(pixels, cpy.pixels, pitch * h);
  	} else
  		pixels = NULL;
  }
  
  RageSurface::~RageSurface()
  {
  
  	if (pixels_owned)
  		delete[] pixels;
  	else if (stb_loadpoint)
  		stbi_image_free(pixels);
  }
  
  static int
  GetShiftFromMask(uint32_t mask)
  {
  	if (!mask)
  		return 0;
  
  	int iShift = 0;
  	while ((mask & 1) == 0) {
  		mask >>= 1;
  		++iShift;
  	}
  	return iShift;
  }
  
  static int
  GetBitsFromMask(uint32_t mask)
  {
  	if (!mask)
  		return 0;
  
  	mask >>= GetShiftFromMask(mask);
  
  	int iBits = 0;
  	while ((mask & 1) == 1) {
  		mask >>= 1;
  		++iBits;
  	}
  	return iBits;
  }
  
  void
  SetupFormat(RageSurfaceFormat& fmt,
  			int width,
  			int height,
  			int BitsPerPixel,
  			uint32_t Rmask,
  			uint32_t Gmask,
  			uint32_t Bmask,
  			uint32_t Amask)
  {
  	fmt.BitsPerPixel = BitsPerPixel;
  	fmt.BytesPerPixel = BitsPerPixel / 8;
  	if (fmt.BytesPerPixel == 1) {
  		ZERO(fmt.Mask);
  		ZERO(fmt.Shift);
  
  		// Loss for paletted textures is zero; the actual palette entries are
  		// 8-bit.
  		ZERO(fmt.Loss);
  
  		fmt.palette = new RageSurfacePalette;
  		fmt.palette->ncolors = 256;
  	} else {
  		fmt.Mask[0] = Rmask;
  		fmt.Mask[1] = Gmask;
  		fmt.Mask[2] = Bmask;
  		fmt.Mask[3] = Amask;
  
  		fmt.Shift[0] = GetShiftFromMask(Rmask);
  		fmt.Shift[1] = GetShiftFromMask(Gmask);
  		fmt.Shift[2] = GetShiftFromMask(Bmask);
  		fmt.Shift[3] = GetShiftFromMask(Amask);
  
  		fmt.Loss[0] = (uint8_t)(8 - GetBitsFromMask(Rmask));
  		fmt.Loss[1] = (uint8_t)(8 - GetBitsFromMask(Gmask));
  		fmt.Loss[2] = (uint8_t)(8 - GetBitsFromMask(Bmask));
  		fmt.Loss[3] = (uint8_t)(8 - GetBitsFromMask(Amask));
  	}
  }
  
  RageSurface*
  CreateSurface(int width,
  			  int height,
  			  int BitsPerPixel,
  			  uint32_t Rmask,
  			  uint32_t Gmask,
  			  uint32_t Bmask,
  			  uint32_t Amask)
  {
  	auto* pImg = new RageSurface;
  
  	SetupFormat(
  	  pImg->fmt, width, height, BitsPerPixel, Rmask, Gmask, Bmask, Amask);
  
  	pImg->w = width;
  	pImg->h = height;
  	pImg->flags = 0;
  	pImg->pitch = width * BitsPerPixel / 8;
  	pImg->pixels = new uint8_t[pImg->pitch * height];
  
  	/*
  	if( BitsPerPixel == 8 )
  	{
  		pImg->fmt.palette = new RageSurfacePalette;
  	}
  	*/
  
  	return pImg;
  }
  
  RageSurface*
  CreateSurfaceFrom(int width,
  				  int height,
  				  int BitsPerPixel,
  				  uint32_t Rmask,
  				  uint32_t Gmask,
  				  uint32_t Bmask,
  				  uint32_t Amask,
  				  uint8_t* pPixels,
  				  uint32_t pitch)
  {
  	auto* pImg = new RageSurface;
  
  	SetupFormat(
  	  pImg->fmt, width, height, BitsPerPixel, Rmask, Gmask, Bmask, Amask);
  
  	pImg->w = width;
  	pImg->h = height;
  	pImg->flags = 0;
  	pImg->pitch = pitch;
  	pImg->pixels = pPixels;
  	pImg->pixels_owned = false;
  
  	return pImg;
  }
  
  /*
   * (c) 2001-2004 Glenn Maynard, Chris Danford
   * All rights reserved.
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the
   * "Software"), to deal in the Software without restriction, including
   * without limitation the rights to use, copy, modify, merge, publish,
   * distribute, and/or sell copies of the Software, and to permit persons to
   * whom the Software is furnished to do so, provided that the above
   * copyright notice(s) and this permission notice appear in all copies of
   * the Software and that both the above copyright notice(s) and this
   * permission notice appear in supporting documentation.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
   * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
   * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
   * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
   * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
   * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
   * PERFORMANCE OF THIS SOFTWARE.
   */
  