904 lines
32 KiB
C++
904 lines
32 KiB
C++
#include <cmath>
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
|
|
#include "cpu.h"
|
|
#include "image.h"
|
|
|
|
#ifndef STAND_ALONE
|
|
#include "editor.h"
|
|
#include "graphics.h"
|
|
#include "menu.h"
|
|
#include "terminal.h"
|
|
#endif
|
|
|
|
|
|
namespace Image
|
|
{
|
|
const int _gtRgbFormatSize[] = {1, 1, 1, 2, 2, 2, 3, 4};
|
|
|
|
bool _hostIsBigEndian = false;
|
|
|
|
double _gammaInput = 1.0;
|
|
double _gammaOutput = 1.0;
|
|
double _diffusionScale = 32.0;
|
|
|
|
DiffusionType _diffusionType = LumError3;
|
|
|
|
|
|
double getGammaInput(void) {return _gammaInput;}
|
|
double getGammaOutput(void) {return _gammaOutput;}
|
|
double getDiffusionScale(void) {return _diffusionScale;}
|
|
DiffusionType getDiffusionType(void) {return _diffusionType;}
|
|
|
|
void setGammaInput(double gammaInput)
|
|
{
|
|
if(gammaInput < 0.5) gammaInput = 0.5;
|
|
if(gammaInput > 5.0) gammaInput = 5.0;
|
|
_gammaInput = gammaInput;
|
|
}
|
|
void setGammaOutput(double gammaOutput)
|
|
{
|
|
if(gammaOutput < 0.5) gammaOutput = 0.5;
|
|
if(gammaOutput > 5.0) gammaOutput = 5.0;
|
|
_gammaOutput = gammaOutput;
|
|
}
|
|
void setDiffusionScale(double diffusionScale)
|
|
{
|
|
if(diffusionScale > 256.0) diffusionScale = 8.0;
|
|
if(diffusionScale < 8.0) diffusionScale = 256.0;
|
|
_diffusionScale = diffusionScale;
|
|
}
|
|
|
|
void setDiffusionType(int diffusionType)
|
|
{
|
|
if(diffusionType < 0) diffusionType = NumDiffusionTypes - 1;
|
|
if(diffusionType > NumDiffusionTypes - 1) diffusionType = 0;
|
|
_diffusionType = (DiffusionType)diffusionType;
|
|
}
|
|
|
|
|
|
void initialise(void)
|
|
{
|
|
#ifndef STAND_ALONE
|
|
std::vector<std::string> items = {"IMAGE ", "Load ", "Save ", "Select", "Delete", "Erase "};
|
|
Menu::createMenu("Image", items, 6, 6);
|
|
#endif
|
|
|
|
if(Cpu::getHostEndianness() == Cpu::BigEndian) _hostIsBigEndian = true;
|
|
}
|
|
|
|
bool getFileSize(const std::string& filename, std::streampos& fileSize)
|
|
{
|
|
std::ifstream infile(filename, std::ios::binary | std::ios::in);
|
|
if(!infile.is_open()) return false;
|
|
|
|
fileSize = infile.tellg();
|
|
infile.seekg(0, std::ios::end);
|
|
fileSize = infile.tellg() - fileSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Currently only supports RGB222 format
|
|
bool loadGtRgbFile(const std::string& filename, GtRgbFile& gtRgbFile)
|
|
{
|
|
std::ifstream infile(filename, std::ios::binary | std::ios::in);
|
|
if(!infile.is_open())
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : failed to open '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
std::streampos fileSize = 0;
|
|
if(!getFileSize(filename, fileSize))
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : couldn't get file size of '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Read header
|
|
GtRgbHeader header;
|
|
infile.read((char *)&header, sizeof(GtRgbHeader));
|
|
if(infile.eof() || infile.bad() || infile.fail())
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : bad header in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
std::string name = std::string(header._name, sizeof(GTRGB_IDENTIFIER));
|
|
if(name.find(GTRGB_IDENTIFIER) == std::string::npos)
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : bad header identifier in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Big endian conversion
|
|
if(_hostIsBigEndian)
|
|
{
|
|
Cpu::swapEndianness(header._format);
|
|
Cpu::swapEndianness(header._width);
|
|
Cpu::swapEndianness(header._height);
|
|
}
|
|
|
|
if(header._format > GtRgbFormats::GT_RGB_888)
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : bad header format : %04x : in '%s'\n", header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
int formatSize = _gtRgbFormatSize[header._format];
|
|
int totalSize = header._width * header._height * formatSize;
|
|
|
|
// Quick sanity check on total size, (maximum RAM is 64K)
|
|
if(totalSize >= 0x10000)
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : image is larger than 64K bytes : width=%d : height=%d : format=%04x : in '%s'\n", header._width, header._height, header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._width == 0 || header._height == 0)
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : width and height both have to be non zero : width=%d : height=%d : format=%04x : in '%s'\n", header._width, header._height, header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
gtRgbFile._header = header;
|
|
gtRgbFile._data.resize(totalSize);
|
|
|
|
// Read data
|
|
infile.read((char *)>RgbFile._data[0], totalSize);
|
|
if(infile.eof() || infile.bad() || infile.fail())
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : bad data in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Optional data
|
|
int optionalSize = int(infile.tellg()) - int(fileSize);
|
|
if(optionalSize < 0 || (optionalSize & 1)) // should never be -ve or odd
|
|
{
|
|
fprintf(stderr, "Image::loadGtRgbFile() : bad optional size in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
else if(optionalSize > 0)
|
|
{
|
|
int numOptionalData = optionalSize / 2;
|
|
gtRgbFile._optional.resize(numOptionalData);
|
|
infile.read((char *)>RgbFile._optional[0], optionalSize);
|
|
|
|
if(_hostIsBigEndian)
|
|
{
|
|
for(int i=0; i<numOptionalData; i++) Cpu::swapEndianness(gtRgbFile._optional[i]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool saveGtRgbFile(const std::string& filename, GtRgbFile& gtRgbFile)
|
|
{
|
|
std::ofstream outfile(filename, std::ios::binary | std::ios::out);
|
|
if(!outfile.is_open())
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : failed to open '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(gtRgbFile._header._format > GtRgbFormats::GT_RGB_888)
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : bad header format : %04x : for '%s'\n", gtRgbFile._header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
int formatSize = _gtRgbFormatSize[gtRgbFile._header._format];
|
|
int totalSize = gtRgbFile._header._width * gtRgbFile._header._height * formatSize;
|
|
|
|
// Quick sanity check on total size, (maximum RAM is 64K)
|
|
if(totalSize >= 0x10000)
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : image is larger than 64K bytes : width=%d : height=%d : format=%04x : in '%s'\n", gtRgbFile._header._width, gtRgbFile._header._height, gtRgbFile._header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(gtRgbFile._header._width == 0 || gtRgbFile._header._height == 0)
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : width and height both have to be non zero : width=%d : height=%d : format=%04x : in '%s'\n", gtRgbFile._header._width, gtRgbFile._header._height, gtRgbFile._header._format, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Wrong size
|
|
if(totalSize != int(gtRgbFile._data.size()))
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : image size does not match data size : image size=%d : data size=%d : in '%s'\n", totalSize, int(gtRgbFile._data.size()), filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Big endian conversion
|
|
if(_hostIsBigEndian)
|
|
{
|
|
Cpu::swapEndianness(gtRgbFile._header._format);
|
|
Cpu::swapEndianness(gtRgbFile._header._width);
|
|
Cpu::swapEndianness(gtRgbFile._header._height);
|
|
}
|
|
|
|
// Write header
|
|
outfile.write((char *)>RgbFile._header, sizeof(GtRgbHeader));
|
|
if(outfile.bad() || outfile.fail())
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : write error in header of '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Big endian conversion
|
|
if(_hostIsBigEndian)
|
|
{
|
|
Cpu::swapEndianness(gtRgbFile._header._format);
|
|
Cpu::swapEndianness(gtRgbFile._header._width);
|
|
Cpu::swapEndianness(gtRgbFile._header._height);
|
|
}
|
|
|
|
// Write data
|
|
outfile.write((char *)>RgbFile._data[0], gtRgbFile._data.size());
|
|
if(outfile.bad() || outfile.fail())
|
|
{
|
|
fprintf(stderr, "Image::saveGtRgbFile() : write error in data of '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Write optional data
|
|
size_t numOptionalData = gtRgbFile._optional.size();
|
|
if(numOptionalData > 0)
|
|
{
|
|
if(_hostIsBigEndian)
|
|
{
|
|
for(int i=0; i<int(numOptionalData); i++) Cpu::swapEndianness(gtRgbFile._optional[i]);
|
|
}
|
|
|
|
outfile.write((char *)>RgbFile._optional[0], numOptionalData*2);
|
|
|
|
if(_hostIsBigEndian)
|
|
{
|
|
for(int i=0; i<int(numOptionalData); i++) Cpu::swapEndianness(gtRgbFile._optional[i]);
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool loadTgaFile(const std::string& filename, TgaFile& tgaFile)
|
|
{
|
|
std::ifstream infile(filename, std::ios::binary | std::ios::in);
|
|
if(!infile.is_open())
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : failed to open '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Read header
|
|
TgaHeader header;
|
|
infile.read((char *)&header, sizeof(TgaHeader));
|
|
if(infile.eof() || infile.bad() || infile.fail())
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad header in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(_hostIsBigEndian)
|
|
{
|
|
Cpu::swapEndianness(header._colourMapLength);
|
|
Cpu::swapEndianness(header._colourMapOrigin);
|
|
Cpu::swapEndianness(header._originX);
|
|
Cpu::swapEndianness(header._originY);
|
|
Cpu::swapEndianness(header._width);
|
|
Cpu::swapEndianness(header._height);
|
|
}
|
|
|
|
if(header._colourMapType != 0)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad colourMapType %d, the only valid colourMapType is 0 : in '%s'\n", header._colourMapType, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._imageType != 2)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad imageType %d, the only valid imageType is 2 : in '%s'\n", header._imageType, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._colourMapDepth !=0 || header._colourMapLength != 0 || header._colourMapOrigin != 0)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad colourMap entries, colour maps, (palettes), not supported : in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._originX !=0 || header._originY != 0)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad origin, origin anything other thant (0, 0), not supported : in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._bitsPerPixel != 24 && header._bitsPerPixel != 32)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad bitsPerPixel, only bitsPerPixel = 8 is supported : in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._width * header._height >= 0x10000)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : maximum width*height = 64Kbytes, width = %d, height =%d, size = %d : in '%s'\n", header._width, header._height, header._width * header._height, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._width == 0 || header._height == 0)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : width and height both have to be non zero, width = %d, height =%d : in '%s'\n", header._width, header._height, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
if(header._imageDescriptor & 0x0F)
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : attribute bits per pixel not supported, attributes = %01x : in '%s'\n", (header._imageDescriptor & 0x0F), filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
tgaFile._imageOrigin = (header._imageDescriptor & 0x30) >>4;
|
|
|
|
// Read image identifer
|
|
std::vector<char> identifier;
|
|
if(header._idLength > 0)
|
|
{
|
|
identifier.resize(header._idLength);
|
|
infile.read((char *)&identifier[0], header._idLength);
|
|
}
|
|
|
|
int totalSize = header._width * header._height * (header._bitsPerPixel / 8);
|
|
|
|
// Quick sanity check on total size, (maximum RAM is 64K)
|
|
if(totalSize >= 0x10000 * (header._bitsPerPixel / 8))
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : image is larger than %d bytes : width=%d : height=%d : in '%s'\n", 0x10000 * (header._bitsPerPixel / 8), header._width, header._height, filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
tgaFile._header = header;
|
|
tgaFile._data.resize(totalSize);
|
|
|
|
// Read data
|
|
infile.read((char *)&tgaFile._data[0], totalSize);
|
|
if(infile.eof() || infile.bad() || infile.fail())
|
|
{
|
|
fprintf(stderr, "Image::loadTgaFile() : bad data in '%s'\n", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int getPixelAddress(int width, int x, int y)
|
|
{
|
|
return y*width + x;
|
|
}
|
|
|
|
uint8_t clamp8Bit(double x)
|
|
{
|
|
return uint8_t(fmax(fmin(x, 255.0), 0.0));
|
|
}
|
|
|
|
uint8_t convertTo2Bits(uint8_t component)
|
|
{
|
|
return uint8_t(double(component) / 255.0 * 3.0 + 0.5) * 85;
|
|
}
|
|
|
|
// *NOTE* TGA and the Gigatron store RGB pixels in little endian order, so BGR in memory
|
|
uint8_t convertRgb24ToRgb6(uint8_t red, uint8_t green, uint8_t blue)
|
|
{
|
|
red = convertTo2Bits(red);
|
|
green = convertTo2Bits(green);
|
|
blue = convertTo2Bits(blue);
|
|
return uint8_t(((blue & 0xC0) >>2) | ((green & 0xC0) >>4) | ((red & 0xC0) >>6));
|
|
}
|
|
|
|
// 24bit to 6bit
|
|
bool convertRGB8toRGB2(const std::vector<uint8_t>& src, std::vector<uint8_t>& dst, int width, int height, uint8_t imageOrigin)
|
|
{
|
|
if(int(src.size()) != width*height*3) return false;
|
|
dst.resize(width*height);
|
|
|
|
int startX = 0, endX = width, stepX = 1;
|
|
int startY = 0, endY = height, stepY = 1;
|
|
|
|
switch(imageOrigin)
|
|
{
|
|
case 0: startX = 0, endX = width, stepX = 1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 1: startX = width-1, endX = -1, stepX = -1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 2: startX = 0, endX = width, stepX = 1; startY = 0, endY = height, stepY = 1; break;
|
|
case 3: startX = width-1, endX = -1, stepX = -1; startY = 0, endY = height, stepY = 1; break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
uint8_t* ptr = &dst[0];
|
|
for(int y=startY; y!=endY; y+=stepY)
|
|
{
|
|
for(int x=startX; x!=endX; x+=stepX)
|
|
{
|
|
int indexRGB2 = getPixelAddress(width, x, y);
|
|
int indexRGB8 = indexRGB2 * 3;
|
|
*ptr++ = convertRgb24ToRgb6(src[indexRGB8 + 2], src[indexRGB8 + 1], src[indexRGB8 + 0]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 32bit to 6bit, (ignoring alpha channel)
|
|
bool convertRGBA8toRGB2(const std::vector<uint8_t>& src, std::vector<uint8_t>& dst, int width, int height, uint8_t imageOrigin)
|
|
{
|
|
if(int(src.size()) != width*height*4) return false;
|
|
dst.resize(width*height);
|
|
|
|
int startX = 0, endX = width, stepX = 1;
|
|
int startY = 0, endY = height, stepY = 1;
|
|
|
|
switch(imageOrigin)
|
|
{
|
|
case 0: startX = 0, endX = width, stepX = 1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 1: startX = width-1, endX = -1, stepX = -1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 2: startX = 0, endX = width, stepX = 1; startY = 0, endY = height, stepY = 1; break;
|
|
case 3: startX = width-1, endX = -1, stepX = -1; startY = 0, endY = height, stepY = 1; break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
uint8_t* ptr = &dst[0];
|
|
for(int y=startY; y!=endY; y+=stepY)
|
|
{
|
|
for(int x=startX; x!=endX; x+=stepX)
|
|
{
|
|
int indexRGB2 = getPixelAddress(width, x, y);
|
|
int indexRGBA8 = indexRGB2 * 4;
|
|
*ptr++ = convertRgb24ToRgb6(src[indexRGBA8 + 2], src[indexRGBA8 + 1], src[indexRGBA8 + 0]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8_t srgbToLin(uint8_t srgb)
|
|
{
|
|
return uint8_t(pow(double(srgb)/255.0, _gammaInput) * 255.0);
|
|
}
|
|
|
|
uint8_t linToSrgb(uint8_t lin)
|
|
{
|
|
return uint8_t(pow(double(lin)/255.0, 1.0/_gammaOutput) * 255.0);
|
|
}
|
|
|
|
// Floyd-Steinberg dithering 24bit to 6bit, (*NOTE* TGA and the Gigatron store RGB pixels in little endian order, so BGR in memory)
|
|
bool ditherRGB8toRGB2(std::vector<uint8_t>& src, std::vector<uint8_t>& dst, int width, int height, uint8_t imageOrigin)
|
|
{
|
|
if(int(src.size()) != width*height*3) return false;
|
|
dst.resize(width*height);
|
|
|
|
int startX = 0, endX = width, stepX = 1;
|
|
int startY = 0, endY = height, stepY = 1;
|
|
|
|
switch(imageOrigin)
|
|
{
|
|
case 0: startX = 0, endX = width, stepX = 1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 1: startX = width-1, endX = -1, stepX = -1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 2: startX = 0, endX = width, stepX = 1; startY = 0, endY = height, stepY = 1; break;
|
|
case 3: startX = width-1, endX = -1, stepX = -1; startY = 0, endY = height, stepY = 1; break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
for(int y=startY; y!=endY; y+=stepY)
|
|
{
|
|
for(int x=startX; x!=endX; x+=stepX)
|
|
{
|
|
int index = getPixelAddress(width, x, y)*3;
|
|
|
|
src[index + 2] = srgbToLin(int(src[index + 2]));
|
|
src[index + 1] = srgbToLin(int(src[index + 1]));
|
|
src[index + 0] = srgbToLin(int(src[index + 0]));
|
|
}
|
|
}
|
|
|
|
for(int y=startY; y!=endY; y+=stepY)
|
|
{
|
|
for(int x=startX; x!=endX; x+=stepX)
|
|
{
|
|
int index = getPixelAddress(width, x, y)*3;
|
|
|
|
uint8_t oldRed = src[index + 2];
|
|
uint8_t oldGrn = src[index + 1];
|
|
uint8_t oldBlu = src[index + 0];
|
|
double nrmRed0 = double(oldRed) / 255.0;
|
|
double nrmGrn0 = double(oldGrn) / 255.0;
|
|
double nrmBlu0 = double(oldBlu) / 255.0;
|
|
|
|
uint8_t newRed = convertTo2Bits(oldRed);
|
|
uint8_t newGrn = convertTo2Bits(oldGrn);
|
|
uint8_t newBlu = convertTo2Bits(oldBlu);
|
|
double nrmRed1 = double(newRed) / 255.0;
|
|
double nrmGrn1 = double(newGrn) / 255.0;
|
|
double nrmBlu1 = double(newBlu) / 255.0;
|
|
|
|
double oldLum = 0.0;
|
|
double newLum = 0.0;
|
|
bool useLumError = true;
|
|
switch(_diffusionType)
|
|
{
|
|
case LumError0:
|
|
{
|
|
oldLum = (nrmRed0 + nrmGrn0 + nrmBlu0) * 0.33333333333;
|
|
newLum = (nrmRed1 + nrmGrn1 + nrmBlu1) * 0.33333333333;
|
|
}
|
|
break;
|
|
|
|
case LumError1:
|
|
{
|
|
oldLum = 0.299*nrmRed0 + 0.587*nrmGrn0 + 0.114*nrmBlu0;
|
|
newLum = 0.299*nrmRed1 + 0.587*nrmGrn1 + 0.114*nrmBlu1;
|
|
}
|
|
break;
|
|
|
|
case LumError2:
|
|
{
|
|
oldLum = sqrt(0.299*(nrmRed0*nrmRed0) + 0.587*(nrmGrn0*nrmGrn0) + 0.114*(nrmBlu0*nrmBlu0));
|
|
newLum = sqrt(0.299*(nrmRed1*nrmRed1) + 0.587*(nrmGrn1*nrmGrn1) + 0.114*(nrmBlu1*nrmBlu1));
|
|
}
|
|
break;
|
|
|
|
case LumError3:
|
|
{
|
|
oldLum = sqrt(0.33333333333*(nrmRed0*nrmRed0) + 0.33333333333*(nrmGrn0*nrmGrn0) + 0.33333333333*(nrmBlu0*nrmBlu0));
|
|
newLum = sqrt(0.33333333333*(nrmRed1*nrmRed1) + 0.33333333333*(nrmGrn1*nrmGrn1) + 0.33333333333*(nrmBlu1*nrmBlu1));
|
|
}
|
|
break;
|
|
|
|
case RgbError:
|
|
{
|
|
useLumError = false;
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
double errorScale = (_diffusionScale > 128.0) ? 0.0 : 1.0;
|
|
double errRed = (useLumError) ? double(oldLum - newLum) * 255.0 * errorScale : double(oldRed - newRed) * errorScale;
|
|
double errGrn = (useLumError) ? double(oldLum - newLum) * 255.0 * errorScale : double(oldGrn - newGrn) * errorScale;
|
|
double errBlu = (useLumError) ? double(oldLum - newLum) * 255.0 * errorScale : double(oldBlu - newBlu) * errorScale;
|
|
|
|
src[index + 2] = linToSrgb(newRed);
|
|
src[index + 1] = linToSrgb(newGrn);
|
|
src[index + 0] = linToSrgb(newBlu);
|
|
|
|
if(x + 1 < width)
|
|
{
|
|
src[getPixelAddress(width, x+1, y)*3 + 2] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y)*3 + 2]) + 7.0*errRed/_diffusionScale));
|
|
src[getPixelAddress(width, x+1, y)*3 + 1] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y)*3 + 1]) + 7.0*errGrn/_diffusionScale));
|
|
src[getPixelAddress(width, x+1, y)*3 + 0] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y)*3 + 0]) + 7.0*errBlu/_diffusionScale));
|
|
}
|
|
|
|
if(x - 1 >= 0 && y + 1 < height)
|
|
{
|
|
src[getPixelAddress(width, x-1, y+1)*3 + 2] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x-1, y+1)*3 + 2]) + 3.0*errRed/_diffusionScale));
|
|
src[getPixelAddress(width, x-1, y+1)*3 + 1] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x-1, y+1)*3 + 1]) + 3.0*errGrn/_diffusionScale));
|
|
src[getPixelAddress(width, x-1, y+1)*3 + 0] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x-1, y+1)*3 + 0]) + 3.0*errBlu/_diffusionScale));
|
|
}
|
|
|
|
if(y + 1 < height)
|
|
{
|
|
src[getPixelAddress(width, x, y+1)*3 + 2] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x, y+1)*3 + 2]) + 5.0*errRed/_diffusionScale));
|
|
src[getPixelAddress(width, x, y+1)*3 + 1] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x, y+1)*3 + 1]) + 5.0*errGrn/_diffusionScale));
|
|
src[getPixelAddress(width, x, y+1)*3 + 0] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x, y+1)*3 + 0]) + 5.0*errBlu/_diffusionScale));
|
|
}
|
|
|
|
if(x + 1 < width && y + 1 < height)
|
|
{
|
|
src[getPixelAddress(width, x+1, y+1)*3 + 2] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y+1)*3 + 2]) + 1.0*errRed/_diffusionScale));
|
|
src[getPixelAddress(width, x+1, y+1)*3 + 1] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y+1)*3 + 1]) + 1.0*errGrn/_diffusionScale));
|
|
src[getPixelAddress(width, x+1, y+1)*3 + 0] = linToSrgb(clamp8Bit(double(src[getPixelAddress(width, x+1, y+1)*3 + 0]) + 1.0*errBlu/_diffusionScale));
|
|
}
|
|
}
|
|
}
|
|
|
|
convertRGB8toRGB2(src, dst, width, height, imageOrigin);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Floyd-Steinberg dithering 32bit to 6bit, (ignoring alpha channel)
|
|
bool ditherRGBA8toRGB2(std::vector<uint8_t>& src, std::vector<uint8_t>& dst, int width, int height, uint8_t imageOrigin)
|
|
{
|
|
if(int(src.size()) != width*height*4) return false;
|
|
dst.resize(width*height);
|
|
|
|
int startX = 0, endX = width, stepX = 1;
|
|
int startY = 0, endY = height, stepY = 1;
|
|
|
|
switch(imageOrigin)
|
|
{
|
|
case 0: startX = 0, endX = width, stepX = 1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 1: startX = width-1, endX = -1, stepX = -1; startY = height-1, endY = -1, stepY = -1; break;
|
|
case 2: startX = 0, endX = width, stepX = 1; startY = 0, endY = height, stepY = 1; break;
|
|
case 3: startX = width-1, endX = -1, stepX = -1; startY = 0, endY = height, stepY = 1; break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
uint8_t* ptr = &dst[0];
|
|
for(int y=startY; y!=endY; y+=stepY)
|
|
{
|
|
for(int x=startX; x!=endX; x+=stepX)
|
|
{
|
|
*ptr = *ptr;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//**********************************************************************************************************************
|
|
//* Editor
|
|
//**********************************************************************************************************************
|
|
#ifndef STAND_ALONE
|
|
enum MenuItem {MenuLoad=0, MenuSave, MenuSel, MenuDel, MenuErase};
|
|
|
|
const std::vector<std::string> _suffixes = {".gtrgb", "tga"};
|
|
|
|
bool _firstTimeRender = true;
|
|
bool _refreshScreen = false;
|
|
|
|
|
|
void refreshScreen(void)
|
|
{
|
|
_firstTimeRender = false;
|
|
_refreshScreen = false;
|
|
|
|
Graphics::clearScreen(0x22222222);
|
|
Graphics::drawText("TODO: Finish this some day", 28*FONT_WIDTH, 30*FONT_HEIGHT, 0xFFFFFFFF, false, 0);
|
|
|
|
Editor::browseDirectory(_suffixes);
|
|
}
|
|
|
|
void handleGuiEvents(SDL_Event& event)
|
|
{
|
|
switch(event.type)
|
|
{
|
|
case SDL_WINDOWEVENT:
|
|
{
|
|
switch(event.window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
{
|
|
Graphics::setWidthHeight(event.window.data1, event.window.data2);
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SDL_QUIT:
|
|
{
|
|
Cpu::shutdown();
|
|
exit(0);
|
|
}
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void handleMouseLeftButtonDown(int mouseX, int mouseY)
|
|
{
|
|
// Normalised mouse position
|
|
float mx = float(mouseX) / float(Graphics::getWidth());
|
|
float my = float(mouseY) / float(Graphics::getHeight());
|
|
int pixelX = int(mx * float(SCREEN_WIDTH));
|
|
int pixelY = int(my * float(SCREEN_HEIGHT));
|
|
fprintf(stderr, "%d %d %f %f %d %d\n", mouseX, mouseY, mx, my, pixelX, pixelY);
|
|
}
|
|
|
|
void handleMouseRightButtonDown(int mouseX, int mouseY)
|
|
{
|
|
Menu::captureItem("Image", mouseX, mouseY);
|
|
Menu::renderMenu("Image");
|
|
}
|
|
|
|
void handleMouseButtonDown(const SDL_Event& event, const Editor::MouseState& mouseState)
|
|
{
|
|
UNREFERENCED_PARAM(event);
|
|
#if 0
|
|
if(mouseState._state == SDL_BUTTON_LEFT)
|
|
#endif
|
|
if(mouseState._state == SDL_BUTTON_X1)
|
|
{
|
|
Menu::captureMenu("Image", mouseState._x, mouseState._y);
|
|
}
|
|
}
|
|
|
|
void handleMouseButtonUp(const SDL_Event& event, const Editor::MouseState& mouseState)
|
|
{
|
|
UNREFERENCED_PARAM(event);
|
|
UNREFERENCED_PARAM(mouseState);
|
|
|
|
int menuItemIndex;
|
|
Menu::getMenuItemIndex("Image", menuItemIndex);
|
|
|
|
switch(menuItemIndex)
|
|
{
|
|
// Load image
|
|
case MenuLoad:
|
|
{
|
|
fprintf(stderr, "Image::Load()\n");
|
|
}
|
|
break;
|
|
|
|
// Save image
|
|
case MenuSave:
|
|
{
|
|
fprintf(stderr, "Image::Save()\n");
|
|
}
|
|
break;
|
|
|
|
// Select mode
|
|
case MenuSel:
|
|
{
|
|
fprintf(stderr, "Image::Select()\n");
|
|
}
|
|
break;
|
|
|
|
// Delete selection
|
|
case MenuDel:
|
|
{
|
|
fprintf(stderr, "Image::Delete()\n");
|
|
}
|
|
break;
|
|
|
|
// Erase screen
|
|
case MenuErase:
|
|
{
|
|
fprintf(stderr, "Image::Erase()\n");
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
_refreshScreen = true;
|
|
}
|
|
|
|
void handleMouseWheel(const SDL_Event& event)
|
|
{
|
|
if(event.wheel.y > 0) return;
|
|
if(event.wheel.y < 0) return;
|
|
}
|
|
|
|
void handleKey(const SDL_Event& event)
|
|
{
|
|
char keyCode = event.text.text[0];
|
|
fprintf(stderr, "%c", keyCode);
|
|
}
|
|
|
|
void handleKeyDown(SDL_Keycode keyCode, Uint16 keyMod)
|
|
{
|
|
// Leave image editor
|
|
if(keyCode == Editor::getEmulatorScanCode("ImageEditor") && keyMod == Editor::getEmulatorKeyMod("ImageEditor"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Editor::setEditorToPrevMode();
|
|
}
|
|
// Quit
|
|
else if(keyCode == Editor::getEmulatorScanCode("Quit") && keyMod == Editor::getEmulatorKeyMod("Quit"))
|
|
{
|
|
Cpu::shutdown();
|
|
exit(0);
|
|
}
|
|
// Image editor
|
|
else if(keyCode == Editor::getEmulatorScanCode("AudioEditor") && keyMod == Editor::getEmulatorKeyMod("AudioEditor"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Editor::setEditorMode(Editor::Audio);
|
|
}
|
|
// Terminal mode
|
|
else if(keyCode == Editor::getEmulatorScanCode("Terminal") && keyMod == Editor::getEmulatorKeyMod("Terminal"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Terminal::switchToTerminal();
|
|
}
|
|
}
|
|
|
|
void handleKeyUp(SDL_Keycode keyCode, Uint16 keyMod)
|
|
{
|
|
// Help screen
|
|
if(keyCode == Editor::getEmulatorScanCode("Help") && keyMod == Editor::getEmulatorKeyMod("Help"))
|
|
{
|
|
static bool helpScreen = false;
|
|
helpScreen = !helpScreen;
|
|
Graphics::setDisplayHelpScreen(helpScreen);
|
|
}
|
|
// Disassembler
|
|
else if(keyCode == Editor::getEmulatorScanCode("Disassembler") && keyMod == Editor::getEmulatorKeyMod("Disassembler"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Editor::setEditorMode(Editor::Dasm);
|
|
}
|
|
// Browser
|
|
else if(keyCode == Editor::getEmulatorScanCode("Browse") && keyMod == Editor::getEmulatorKeyMod("Browse"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Editor::setEditorMode(Editor::Load);
|
|
Editor::browseDirectory();
|
|
}
|
|
// Hex monitor
|
|
else if(keyCode == Editor::getEmulatorScanCode("HexMonitor") && keyMod == Editor::getEmulatorKeyMod("HexMonitor"))
|
|
{
|
|
_firstTimeRender = true;
|
|
Editor::setEditorMode(Editor::Hex);
|
|
}
|
|
}
|
|
|
|
void handleInput(void)
|
|
{
|
|
// Mouse button state
|
|
Editor::MouseState mouseState;
|
|
mouseState._state = SDL_GetMouseState(&mouseState._x, &mouseState._y);
|
|
|
|
SDL_Event event;
|
|
while(SDL_PollEvent(&event))
|
|
{
|
|
SDL_Keycode keyCode = event.key.keysym.sym;
|
|
Uint16 keyMod = event.key.keysym.mod & (KMOD_LCTRL | KMOD_LALT | KMOD_LSHIFT);
|
|
|
|
mouseState._state = SDL_GetMouseState(&mouseState._x, &mouseState._y);
|
|
|
|
handleGuiEvents(event);
|
|
|
|
switch(event.type)
|
|
{
|
|
case SDL_MOUSEBUTTONDOWN: handleMouseButtonDown(event, mouseState); break;
|
|
case SDL_MOUSEBUTTONUP: handleMouseButtonUp(event, mouseState); break;
|
|
case SDL_MOUSEWHEEL: handleMouseWheel(event); break;
|
|
case SDL_TEXTINPUT: handleKey(event); break;
|
|
case SDL_KEYDOWN: handleKeyDown(keyCode, keyMod); break;
|
|
case SDL_KEYUP: handleKeyUp(keyCode, keyMod); break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
switch(mouseState._state)
|
|
{
|
|
case SDL_BUTTON_LEFT: handleMouseLeftButtonDown(mouseState._x, mouseState._y); break;
|
|
case SDL_BUTTON_X1: handleMouseRightButtonDown(mouseState._x, mouseState._y); break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void process(void)
|
|
{
|
|
if(_firstTimeRender || _refreshScreen)
|
|
{
|
|
refreshScreen();
|
|
}
|
|
|
|
handleInput();
|
|
Graphics::render(true);
|
|
}
|
|
#endif
|
|
}
|