mirror of https://github.com/bsnes-emu/bsnes.git
507 lines
15 KiB
C++
507 lines
15 KiB
C++
#ifndef NALL_IMAGE_HPP
|
|
#define NALL_IMAGE_HPP
|
|
|
|
#include <nall/bmp.hpp>
|
|
#include <nall/filemap.hpp>
|
|
#include <nall/interpolation.hpp>
|
|
#include <nall/png.hpp>
|
|
#include <nall/stdint.hpp>
|
|
#include <algorithm>
|
|
|
|
namespace nall {
|
|
|
|
struct image {
|
|
uint8_t* data = nullptr;
|
|
unsigned width = 0;
|
|
unsigned height = 0;
|
|
unsigned pitch = 0;
|
|
|
|
bool endian = 0;
|
|
unsigned depth = 32;
|
|
unsigned stride = 4;
|
|
|
|
struct Channel {
|
|
uint64_t mask;
|
|
unsigned depth;
|
|
unsigned shift;
|
|
|
|
inline bool operator==(const Channel& source) {
|
|
return mask == source.mask && depth == source.depth && shift == source.shift;
|
|
}
|
|
|
|
inline bool operator!=(const Channel& source) {
|
|
return !operator==(source);
|
|
}
|
|
};
|
|
|
|
Channel alpha = {255u << 24, 8u, 24};
|
|
Channel red = {255u << 16, 8u, 16};
|
|
Channel green = {255u << 8, 8u, 8};
|
|
Channel blue = {255u << 0, 8u, 0};
|
|
|
|
typedef double (*interpolation)(double, double, double, double, double);
|
|
static inline unsigned bitDepth(uint64_t color);
|
|
static inline unsigned bitShift(uint64_t color);
|
|
static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth);
|
|
|
|
inline bool operator==(const image& source);
|
|
inline bool operator!=(const image& source);
|
|
|
|
inline image& operator=(const image& source);
|
|
inline image& operator=(image&& source);
|
|
inline image(const image& source);
|
|
inline image(image&& source);
|
|
inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask);
|
|
inline image(const string& filename);
|
|
inline image(const uint8_t* data, unsigned size);
|
|
inline image();
|
|
inline ~image();
|
|
|
|
inline uint64_t read(const uint8_t* data) const;
|
|
inline void write(uint8_t* data, uint64_t value) const;
|
|
|
|
inline void free();
|
|
inline bool empty() const;
|
|
inline void allocate(unsigned width, unsigned height);
|
|
inline void clear(uint64_t color);
|
|
inline bool load(const string& filename);
|
|
//inline bool loadBMP(const uint8_t* data, unsigned size);
|
|
inline bool loadPNG(const uint8_t* data, unsigned size);
|
|
inline void scale(unsigned width, unsigned height, interpolation op);
|
|
inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask);
|
|
inline void alphaBlend(uint64_t alphaColor);
|
|
|
|
protected:
|
|
inline uint64_t interpolate(double mu, const uint64_t* s, interpolation op);
|
|
inline void scaleX(unsigned width, interpolation op);
|
|
inline void scaleY(unsigned height, interpolation op);
|
|
inline bool loadBMP(const string& filename);
|
|
inline bool loadPNG(const string& filename);
|
|
};
|
|
|
|
//static
|
|
|
|
unsigned image::bitDepth(uint64_t color) {
|
|
unsigned depth = 0;
|
|
if(color) while((color & 1) == 0) color >>= 1;
|
|
while((color & 1) == 1) { color >>= 1; depth++; }
|
|
return depth;
|
|
}
|
|
|
|
unsigned image::bitShift(uint64_t color) {
|
|
unsigned shift = 0;
|
|
if(color) while((color & 1) == 0) { color >>= 1; shift++; }
|
|
return shift;
|
|
}
|
|
|
|
uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) {
|
|
while(sourceDepth < targetDepth) {
|
|
color = (color << sourceDepth) | color;
|
|
sourceDepth += sourceDepth;
|
|
}
|
|
if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth);
|
|
return color;
|
|
}
|
|
|
|
//public
|
|
|
|
bool image::operator==(const image& source) {
|
|
if(width != source.width) return false;
|
|
if(height != source.height) return false;
|
|
if(pitch != source.pitch) return false;
|
|
|
|
if(endian != source.endian) return false;
|
|
if(stride != source.stride) return false;
|
|
|
|
if(alpha != source.alpha) return false;
|
|
if(red != source.red) return false;
|
|
if(green != source.green) return false;
|
|
if(blue != source.blue) return false;
|
|
|
|
return memcmp(data, source.data, width * height * stride) == 0;
|
|
}
|
|
|
|
bool image::operator!=(const image& source) {
|
|
return !operator==(source);
|
|
}
|
|
|
|
image& image::operator=(const image& source) {
|
|
free();
|
|
|
|
width = source.width;
|
|
height = source.height;
|
|
pitch = source.pitch;
|
|
|
|
endian = source.endian;
|
|
stride = source.stride;
|
|
|
|
alpha = source.alpha;
|
|
red = source.red;
|
|
green = source.green;
|
|
blue = source.blue;
|
|
|
|
data = new uint8_t[width * height * stride];
|
|
memcpy(data, source.data, width * height * stride);
|
|
return *this;
|
|
}
|
|
|
|
image& image::operator=(image&& source) {
|
|
free();
|
|
|
|
width = source.width;
|
|
height = source.height;
|
|
pitch = source.pitch;
|
|
|
|
endian = source.endian;
|
|
stride = source.stride;
|
|
|
|
alpha = source.alpha;
|
|
red = source.red;
|
|
green = source.green;
|
|
blue = source.blue;
|
|
|
|
data = source.data;
|
|
source.data = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
image::image(const image& source) {
|
|
operator=(source);
|
|
}
|
|
|
|
image::image(image&& source) {
|
|
operator=(std::forward<image>(source));
|
|
}
|
|
|
|
image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) {
|
|
this->endian = endian;
|
|
this->depth = depth;
|
|
this->stride = (depth / 8) + ((depth & 7) > 0);
|
|
|
|
alpha = {alphaMask, bitDepth(alphaMask), bitShift(alphaMask)};
|
|
red = {redMask, bitDepth(redMask), bitShift(redMask)};
|
|
green = {greenMask, bitDepth(greenMask), bitShift(greenMask)};
|
|
blue = {blueMask, bitDepth(blueMask), bitShift(blueMask)};
|
|
}
|
|
|
|
image::image(const string& filename) {
|
|
load(filename);
|
|
}
|
|
|
|
image::image(const uint8_t* data, unsigned size) {
|
|
loadPNG(data, size);
|
|
}
|
|
|
|
image::image() {
|
|
}
|
|
|
|
image::~image() {
|
|
free();
|
|
}
|
|
|
|
uint64_t image::read(const uint8_t* data) const {
|
|
uint64_t result = 0;
|
|
if(endian == 0) {
|
|
for(signed n = stride - 1; n >= 0; n--) result = (result << 8) | data[n];
|
|
} else {
|
|
for(signed n = 0; n < stride; n++) result = (result << 8) | data[n];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void image::write(uint8_t* data, uint64_t value) const {
|
|
if(endian == 0) {
|
|
for(signed n = 0; n < stride; n++) { data[n] = value; value >>= 8; }
|
|
} else {
|
|
for(signed n = stride - 1; n >= 0; n--) { data[n] = value; value >>= 8; }
|
|
}
|
|
}
|
|
|
|
void image::free() {
|
|
if(data) delete[] data;
|
|
data = nullptr;
|
|
}
|
|
|
|
bool image::empty() const {
|
|
if(data == nullptr) return true;
|
|
if(width == 0 || height == 0) return true;
|
|
return false;
|
|
}
|
|
|
|
void image::allocate(unsigned width, unsigned height) {
|
|
if(data != nullptr && this->width == width && this->height == height) return;
|
|
free();
|
|
data = new uint8_t[width * height * stride]();
|
|
pitch = width * stride;
|
|
this->width = width;
|
|
this->height = height;
|
|
}
|
|
|
|
void image::clear(uint64_t color) {
|
|
uint8_t *dp = data;
|
|
for(unsigned n = 0; n < width * height; n++) {
|
|
write(dp, color);
|
|
dp += stride;
|
|
}
|
|
}
|
|
|
|
bool image::load(const string& filename) {
|
|
if(loadBMP(filename) == true) return true;
|
|
if(loadPNG(filename) == true) return true;
|
|
return false;
|
|
}
|
|
|
|
void image::scale(unsigned outputWidth, unsigned outputHeight, interpolation op) {
|
|
if(width != outputWidth) scaleX(outputWidth, op);
|
|
if(height != outputHeight) scaleY(outputHeight, op);
|
|
}
|
|
|
|
void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) {
|
|
image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask);
|
|
output.allocate(width, height);
|
|
|
|
#pragma omp parallel for
|
|
for(unsigned y = 0; y < height; y++) {
|
|
uint8_t* dp = output.data + output.pitch * y;
|
|
uint8_t* sp = data + pitch * y;
|
|
for(unsigned x = 0; x < width; x++) {
|
|
uint64_t color = read(sp);
|
|
sp += stride;
|
|
|
|
uint64_t a = (color & alpha.mask) >> alpha.shift;
|
|
uint64_t r = (color & red.mask) >> red.shift;
|
|
uint64_t g = (color & green.mask) >> green.shift;
|
|
uint64_t b = (color & blue.mask) >> blue.shift;
|
|
|
|
a = normalize(a, alpha.depth, output.alpha.depth);
|
|
r = normalize(r, red.depth, output.red.depth);
|
|
g = normalize(g, green.depth, output.green.depth);
|
|
b = normalize(b, blue.depth, output.blue.depth);
|
|
|
|
output.write(dp, (a << output.alpha.shift) | (r << output.red.shift) | (g << output.green.shift) | (b << output.blue.shift));
|
|
dp += output.stride;
|
|
}
|
|
}
|
|
|
|
operator=(std::move(output));
|
|
}
|
|
|
|
void image::alphaBlend(uint64_t alphaColor) {
|
|
uint64_t alphaR = (alphaColor & red.mask) >> red.shift;
|
|
uint64_t alphaG = (alphaColor & green.mask) >> green.shift;
|
|
uint64_t alphaB = (alphaColor & blue.mask) >> blue.shift;
|
|
|
|
#pragma omp parallel for
|
|
for(unsigned y = 0; y < height; y++) {
|
|
uint8_t* dp = data + pitch * y;
|
|
for(unsigned x = 0; x < width; x++) {
|
|
uint64_t color = read(dp);
|
|
|
|
uint64_t colorA = (color & alpha.mask) >> alpha.shift;
|
|
uint64_t colorR = (color & red.mask) >> red.shift;
|
|
uint64_t colorG = (color & green.mask) >> green.shift;
|
|
uint64_t colorB = (color & blue.mask) >> blue.shift;
|
|
double alphaScale = (double)colorA / (double)((1 << alpha.depth) - 1);
|
|
|
|
colorA = (1 << alpha.depth) - 1;
|
|
colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale));
|
|
colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale));
|
|
colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale));
|
|
|
|
write(dp, (colorA << alpha.shift) | (colorR << red.shift) | (colorG << green.shift) | (colorB << blue.shift));
|
|
dp += stride;
|
|
}
|
|
}
|
|
}
|
|
|
|
//protected
|
|
|
|
uint64_t image::interpolate(double mu, const uint64_t* s, double (*op)(double, double, double, double, double)) {
|
|
uint64_t aa = (s[0] & alpha.mask) >> alpha.shift, ar = (s[0] & red.mask) >> red.shift,
|
|
ag = (s[0] & green.mask) >> green.shift, ab = (s[0] & blue.mask) >> blue.shift;
|
|
uint64_t ba = (s[1] & alpha.mask) >> alpha.shift, br = (s[1] & red.mask) >> red.shift,
|
|
bg = (s[1] & green.mask) >> green.shift, bb = (s[1] & blue.mask) >> blue.shift;
|
|
uint64_t ca = (s[2] & alpha.mask) >> alpha.shift, cr = (s[2] & red.mask) >> red.shift,
|
|
cg = (s[2] & green.mask) >> green.shift, cb = (s[2] & blue.mask) >> blue.shift;
|
|
uint64_t da = (s[3] & alpha.mask) >> alpha.shift, dr = (s[3] & red.mask) >> red.shift,
|
|
dg = (s[3] & green.mask) >> green.shift, db = (s[3] & blue.mask) >> blue.shift;
|
|
|
|
int64_t A = op(mu, aa, ba, ca, da);
|
|
int64_t R = op(mu, ar, br, cr, dr);
|
|
int64_t G = op(mu, ag, bg, cg, dg);
|
|
int64_t B = op(mu, ab, bb, cb, db);
|
|
|
|
A = max(0, min(A, (1 << alpha.depth) - 1));
|
|
R = max(0, min(R, (1 << red.depth) - 1));
|
|
G = max(0, min(G, (1 << green.depth) - 1));
|
|
B = max(0, min(B, (1 << blue.depth) - 1));
|
|
|
|
return (A << alpha.shift) | (R << red.shift) | (G << green.shift) | (B << blue.shift);
|
|
}
|
|
|
|
void image::scaleX(unsigned outputWidth, interpolation op) {
|
|
uint8_t* outputData = new uint8_t[outputWidth * height * stride];
|
|
unsigned outputPitch = outputWidth * stride;
|
|
double step = (double)width / (double)outputWidth;
|
|
const uint8_t* terminal = data + pitch * height;
|
|
|
|
#pragma omp parallel for
|
|
for(unsigned y = 0; y < height; y++) {
|
|
uint8_t* dp = outputData + outputPitch * y;
|
|
uint8_t* sp = data + pitch * y;
|
|
|
|
double fraction = 0.0;
|
|
uint64_t s[4] = {sp < terminal ? read(sp) : 0}; //B,C (0,1) = center of kernel { 0, 0, 1, 2 }
|
|
s[1] = s[0];
|
|
s[2] = sp + stride < terminal ? read(sp += stride) : s[1];
|
|
s[3] = sp + stride < terminal ? read(sp += stride) : s[2];
|
|
|
|
for(unsigned x = 0; x < width; x++) {
|
|
while(fraction <= 1.0) {
|
|
if(dp >= outputData + outputPitch * height) break;
|
|
write(dp, interpolate(fraction, (const uint64_t*)&s, op));
|
|
dp += stride;
|
|
fraction += step;
|
|
}
|
|
|
|
s[0] = s[1]; s[1] = s[2]; s[2] = s[3];
|
|
if(sp + stride < terminal) s[3] = read(sp += stride);
|
|
fraction -= 1.0;
|
|
}
|
|
}
|
|
|
|
free();
|
|
data = outputData;
|
|
width = outputWidth;
|
|
pitch = width * stride;
|
|
}
|
|
|
|
void image::scaleY(unsigned outputHeight, interpolation op) {
|
|
uint8_t* outputData = new uint8_t[width * outputHeight * stride];
|
|
double step = (double)height / (double)outputHeight;
|
|
const uint8_t* terminal = data + pitch * height;
|
|
|
|
#pragma omp parallel for
|
|
for(unsigned x = 0; x < width; x++) {
|
|
uint8_t* dp = outputData + stride * x;
|
|
uint8_t* sp = data + stride * x;
|
|
|
|
double fraction = 0.0;
|
|
uint64_t s[4] = {sp < terminal ? read(sp) : 0};
|
|
s[1] = s[0];
|
|
s[2] = sp + pitch < terminal ? read(sp += pitch) : s[1];
|
|
s[3] = sp + pitch < terminal ? read(sp += pitch) : s[2];
|
|
|
|
for(unsigned y = 0; y < height; y++) {
|
|
while(fraction <= 1.0) {
|
|
if(dp >= outputData + pitch * outputHeight) break;
|
|
write(dp, interpolate(fraction, (const uint64_t*)&s, op));
|
|
dp += pitch;
|
|
fraction += step;
|
|
}
|
|
|
|
s[0] = s[1]; s[1] = s[2]; s[2] = s[3];
|
|
if(sp + pitch < terminal) s[3] = read(sp += pitch);
|
|
fraction -= 1.0;
|
|
}
|
|
}
|
|
|
|
free();
|
|
data = outputData;
|
|
height = outputHeight;
|
|
}
|
|
|
|
bool image::loadBMP(const string& filename) {
|
|
uint32_t* outputData;
|
|
unsigned outputWidth, outputHeight;
|
|
if(bmp::read(filename, outputData, outputWidth, outputHeight) == false) return false;
|
|
|
|
allocate(outputWidth, outputHeight);
|
|
const uint32_t* sp = outputData;
|
|
uint8_t* dp = data;
|
|
|
|
for(unsigned y = 0; y < outputHeight; y++) {
|
|
for(unsigned x = 0; x < outputWidth; x++) {
|
|
uint32_t color = *sp++;
|
|
uint64_t a = normalize((uint8_t)(color >> 24), 8, alpha.depth);
|
|
uint64_t r = normalize((uint8_t)(color >> 16), 8, red.depth);
|
|
uint64_t g = normalize((uint8_t)(color >> 8), 8, green.depth);
|
|
uint64_t b = normalize((uint8_t)(color >> 0), 8, blue.depth);
|
|
write(dp, (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift));
|
|
dp += stride;
|
|
}
|
|
}
|
|
|
|
delete[] outputData;
|
|
return true;
|
|
}
|
|
|
|
bool image::loadPNG(const uint8_t* pngData, unsigned pngSize) {
|
|
png source;
|
|
if(source.decode(pngData, pngSize) == false) return false;
|
|
|
|
allocate(source.info.width, source.info.height);
|
|
const uint8_t* sp = source.data;
|
|
uint8_t* dp = data;
|
|
|
|
auto decode = [&]() -> uint64_t {
|
|
uint64_t p, r, g, b, a;
|
|
|
|
switch(source.info.colorType) {
|
|
case 0: //L
|
|
r = g = b = source.readbits(sp);
|
|
a = (1 << source.info.bitDepth) - 1;
|
|
break;
|
|
case 2: //R,G,B
|
|
r = source.readbits(sp);
|
|
g = source.readbits(sp);
|
|
b = source.readbits(sp);
|
|
a = (1 << source.info.bitDepth) - 1;
|
|
break;
|
|
case 3: //P
|
|
p = source.readbits(sp);
|
|
r = source.info.palette[p][0];
|
|
g = source.info.palette[p][1];
|
|
b = source.info.palette[p][2];
|
|
a = (1 << source.info.bitDepth) - 1;
|
|
break;
|
|
case 4: //L,A
|
|
r = g = b = source.readbits(sp);
|
|
a = source.readbits(sp);
|
|
break;
|
|
case 6: //R,G,B,A
|
|
r = source.readbits(sp);
|
|
g = source.readbits(sp);
|
|
b = source.readbits(sp);
|
|
a = source.readbits(sp);
|
|
break;
|
|
}
|
|
|
|
a = normalize(a, source.info.bitDepth, alpha.depth);
|
|
r = normalize(r, source.info.bitDepth, red.depth);
|
|
g = normalize(g, source.info.bitDepth, green.depth);
|
|
b = normalize(b, source.info.bitDepth, blue.depth);
|
|
|
|
return (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift);
|
|
};
|
|
|
|
for(unsigned y = 0; y < height; y++) {
|
|
for(unsigned x = 0; x < width; x++) {
|
|
write(dp, decode());
|
|
dp += stride;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool image::loadPNG(const string& filename) {
|
|
filemap map;
|
|
if(map.open(filename, filemap::mode::read) == false) return false;
|
|
return loadPNG(map.data(), map.size());
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|