more cleanup, crammed DC texconv into here for later use
This commit is contained in:
parent
ba9f5c8323
commit
8298fcec2d
6
Makefile
6
Makefile
@ -54,7 +54,7 @@ FLAGS += -DUF_DEV_ENV
|
||||
|
||||
ifneq (,$(findstring win64,$(ARCH)))
|
||||
ifneq (,$(findstring -DUF_DEV_ENV,$(FLAGS)))
|
||||
REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr cpptrace vall_e # ncurses openvr draco discord bullet ultralight-ux
|
||||
REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr cpptrace vall_e dc:texconv # ncurses openvr draco discord bullet ultralight-ux
|
||||
FLAGS += -march=native -g # -flto # -g
|
||||
endif
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav
|
||||
@ -130,6 +130,10 @@ ifneq (,$(findstring json,$(REQ_DEPS)))
|
||||
endif
|
||||
ifneq (,$(findstring gltf,$(REQ_DEPS)))
|
||||
FLAGS += -DUF_USE_GLTF
|
||||
INCS += -I./dep/include/stb/ # saves having to edit the file
|
||||
endif
|
||||
ifneq (,$(findstring dc:texconv,$(REQ_DEPS)))
|
||||
FLAGS += -DUF_USE_DC_TEXCONV
|
||||
endif
|
||||
ifneq (,$(findstring openal,$(REQ_DEPS)))
|
||||
FLAGS += -DUF_USE_OPENAL -DUF_USE_ALUT
|
||||
|
@ -339,7 +339,7 @@
|
||||
"streams by default": true
|
||||
},
|
||||
"memory pool": {
|
||||
"enabled": true,
|
||||
"enabled": true, // needs to be kept on
|
||||
"subPools": true,
|
||||
"alignment": 64,
|
||||
"override": false,
|
||||
|
@ -78,7 +78,7 @@
|
||||
"stream": {
|
||||
"tag": "worldspawn",
|
||||
"player": "info_player_spawn",
|
||||
"enabled": "auto",
|
||||
"enabled": false, // "auto",
|
||||
"radius": 32,
|
||||
"every": 1
|
||||
}
|
||||
|
@ -81,8 +81,8 @@
|
||||
"streams by default": true
|
||||
},
|
||||
"memory pool": {
|
||||
"enabled": false,
|
||||
"subPools": true,
|
||||
"enabled": true, // probably should be enabled
|
||||
"subPools": false,
|
||||
"alignment": 4,
|
||||
"override": false,
|
||||
"size": "512 KiB",
|
||||
|
@ -20,3 +20,6 @@ Some dependencies of mine rely on some modifications of the original dependencie
|
||||
* [KallistiOS/KallistiOS](https://github.com/KallistiOS/KallistiOS/tree/e4171afbdaf20574a554d8d40f08552e0680db14): Dreamcast only; specifically commit `e4171afb` .
|
||||
* this version requires commenting out this line at [kernel/arch/dreamcast/hardware/cdrom.c#L839](https://github.com/KallistiOS/KallistiOS/blob/e4171afbdaf20574a554d8d40f08552e0680db14/kernel/arch/dreamcast/hardware/cdrom.c#L839) to boot under emulators (I've yet to test )
|
||||
* later version require compiling with `kos-c++` instead of `$(KOS_CCPLUS)` and removing the `-T/opt/dreamcast/kos/utils/ldscripts/shlelf.xc` line when compiling the `.elf`, but crashes later during some `maple` initialization.
|
||||
* [tvspelsfreak/texconv](https://github.com/e-c-k-e-r/texconv): For converting images to `.dtex` for the Dreamcast.
|
||||
* this *is* actually included to allow scene processing to automatically output processed `.dtex` textures...
|
||||
* ...*or* allow files to be converted on the fly on the Dreamcast (although this is a *bad* idea for large image files)
|
File diff suppressed because it is too large
Load Diff
62
dep/include/texconv/common.h
Normal file
62
dep/include/texconv/common.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include <texconv/vqtools.h> // contains some cruft
|
||||
|
||||
#define PIXELFORMAT_ARGB1555 0
|
||||
#define PIXELFORMAT_RGB565 1
|
||||
#define PIXELFORMAT_ARGB4444 2
|
||||
#define PIXELFORMAT_YUV422 3
|
||||
#define PIXELFORMAT_BUMPMAP 4
|
||||
#define PIXELFORMAT_PAL4BPP 5
|
||||
#define PIXELFORMAT_PAL8BPP 6
|
||||
#define PIXELFORMAT_MASK 7
|
||||
#define PIXELFORMAT_SHIFT 27
|
||||
|
||||
#define FLAG_NONTWIDDLED (1 << 26)
|
||||
#define FLAG_STRIDED (1 << 25)
|
||||
#define FLAG_COMPRESSED (1 << 30)
|
||||
#define FLAG_MIPMAPPED (1 << 31)
|
||||
|
||||
#define TEXTURE_SIZE_MIN 8
|
||||
#define TEXTURE_SIZE_MAX 1024
|
||||
#define TEXTURE_STRIDE_MIN 32
|
||||
#define TEXTURE_STRIDE_MAX 992
|
||||
|
||||
#define MIN_MIPMAP_VQ 2
|
||||
#define MIN_MIPMAP_PALVQ 4
|
||||
|
||||
#define TEXTURE_MAGIC "DTEX"
|
||||
#define PALETTE_MAGIC "DPAL"
|
||||
|
||||
#define MIPMAP_OFFSET_4BPP 1
|
||||
#define MIPMAP_OFFSET_8BPP 3
|
||||
#define MIPMAP_OFFSET_16BPP 6
|
||||
|
||||
|
||||
int nextPowerOfTwo(int x);
|
||||
bool isValidSize(int width, int height, int textureType);
|
||||
void writeZeroes(std::ostream& stream, int n);
|
||||
|
||||
bool isFormat(int textureType, int pixelFormat);
|
||||
bool isPaletted(int textureType);
|
||||
bool is16BPP(int textureType);
|
||||
|
||||
|
||||
uint16_t to16BPP(const RGBA& px, int pixelFormat);
|
||||
RGBA to32BPP(uint16_t px, int pixelFormat);
|
||||
|
||||
|
||||
void RGBtoYUV422(const RGBA& rgb1, const RGBA& rgb2, uint16_t& yuv1, uint16_t& yuv2);
|
||||
void YUV422toRGB(const uint16_t yuv1, const uint16_t yuv2, RGBA& rgb1, RGBA& rgb2);
|
||||
|
||||
int writeTextureHeader(std::ostream& stream, int width, int height, int textureType);
|
||||
|
||||
uint32_t combineHash(const RGBA& rgba, uint32_t seed);
|
||||
|
||||
class ImageContainer;
|
||||
|
||||
void convert16BPP(std::ostream& stream, const ImageContainer& images, int textureType);
|
||||
void convertPaletted(std::ostream& stream, const ImageContainer& images, int textureType, const uf::stl::string& palFilename);
|
||||
bool generatePreview(const uf::stl::string& textureFilename, const uf::stl::string& paletteFilename, const uf::stl::string& previewFilename, const uf::stl::string& codeUsageFilename);
|
32
dep/include/texconv/image.h
Normal file
32
dep/include/texconv/image.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <texconv/common.h>
|
||||
|
||||
class Image {
|
||||
public:
|
||||
Image();
|
||||
Image(int width, int height);
|
||||
|
||||
bool loadFromFile(const uf::stl::string& path);
|
||||
bool saveToFile(const uf::stl::string& path) const;
|
||||
|
||||
int width() const;
|
||||
int height() const;
|
||||
|
||||
RGBA pixel(int x,int y) const;
|
||||
void setPixel(int x,int y, RGBA pixel);
|
||||
|
||||
Image scaled(int newW,int newH,bool nearest) const;
|
||||
|
||||
void allocateIndexed(int colors);
|
||||
void setIndexedPixel(int x,int y, uint8_t index);
|
||||
uint8_t indexedPixelAt(int x,int y) const;
|
||||
|
||||
bool isIndexed() const { return indexedMode; }
|
||||
|
||||
private:
|
||||
int w,h;
|
||||
bool indexedMode;
|
||||
uf::stl::vector<RGBA> pixels;
|
||||
uf::stl::vector<uint8_t> indexed;
|
||||
};
|
27
dep/include/texconv/imagecontainer.h
Normal file
27
dep/include/texconv/imagecontainer.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <texconv/image.h>
|
||||
|
||||
class ImageContainer {
|
||||
public:
|
||||
|
||||
bool load(const uf::stl::vector<uf::stl::string>& filenames, int textureType, int mipmapFilter);
|
||||
|
||||
void unloadAll();
|
||||
|
||||
bool hasMipmaps() const { return images.size() > 1; }
|
||||
bool hasSize(int size) const { return images.find(size) != images.end(); }
|
||||
|
||||
const Image& getByIndex(int index, bool ascending=true) const;
|
||||
const Image& getBySize(int size) const { return images.at(size); }
|
||||
|
||||
int imageCount() const { return (int)images.size(); }
|
||||
int width() const { return textureWidth; }
|
||||
int height() const { return textureHeight; }
|
||||
|
||||
private:
|
||||
int textureWidth = 0;
|
||||
int textureHeight = 0;
|
||||
uf::stl::unordered_map<int, Image> images;
|
||||
uf::stl::vector<int> keys;
|
||||
};
|
26
dep/include/texconv/palette.h
Normal file
26
dep/include/texconv/palette.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <texconv/common.h>
|
||||
|
||||
class ImageContainer;
|
||||
|
||||
class Palette {
|
||||
public:
|
||||
Palette() = default;
|
||||
Palette(const ImageContainer& images);
|
||||
|
||||
int colorCount() const { return (int)colorsVec.size(); }
|
||||
void clear() { colorsMap.clear(); colorsVec.clear(); }
|
||||
|
||||
void insert(uint32_t argb);
|
||||
|
||||
int indexOf(uint32_t argb) const;
|
||||
uint32_t colorAt(int index) const;
|
||||
|
||||
bool load(const uf::stl::string& filename);
|
||||
bool save(const uf::stl::string& filename) const;
|
||||
|
||||
private:
|
||||
uf::stl::unordered_map<uint32_t,int> colorsMap;
|
||||
uf::stl::vector<uint32_t> colorsVec;
|
||||
};
|
19
dep/include/texconv/twiddler.h
Normal file
19
dep/include/texconv/twiddler.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
class Twiddler {
|
||||
public:
|
||||
|
||||
Twiddler(int w, int h);
|
||||
~Twiddler();
|
||||
|
||||
int index(int x, int y) const { return m_index[y * m_width + x]; }
|
||||
int index(int i) const { return m_index[i]; }
|
||||
|
||||
private:
|
||||
|
||||
int twiddle(int* output, int stride, int x, int y, int blocksize, int seq) const;
|
||||
|
||||
int m_width;
|
||||
int m_height;
|
||||
int* m_index;
|
||||
};
|
457
dep/include/texconv/vqtools.h
Normal file
457
dep/include/texconv/vqtools.h
Normal file
@ -0,0 +1,457 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
|
||||
// makes life easier
|
||||
#if UF_USE_DC_TEXCONV
|
||||
#include <uf/utils/memory/string.h>
|
||||
#include <uf/utils/memory/vector.h>
|
||||
#include <uf/utils/memory/unordered_map.h>
|
||||
#else
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace uf {
|
||||
namespace stl {
|
||||
using unordered_map = std::unordered_map;
|
||||
using vector = std::vector;
|
||||
using string = std::string;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef uint32_t uint;
|
||||
|
||||
enum FilterMode {
|
||||
NEAREST, BILINEAR
|
||||
};
|
||||
|
||||
struct RGBA {
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
// N-dimensional vectors, for input to a VectorQuantizer.
|
||||
template <uint N>
|
||||
class Vec {
|
||||
public:
|
||||
Vec(uint hval = 0) : hashVal(hval) {}
|
||||
Vec(const Vec<N>& other);
|
||||
void zero();
|
||||
void operator= (const Vec<N>& other);
|
||||
bool operator== (const Vec<N>& other) const;
|
||||
void operator+= (const Vec<N>& other);
|
||||
void operator-= (const Vec<N>& other);
|
||||
Vec<N> operator+ (const Vec<N>& other) const;
|
||||
Vec<N> operator- (const Vec<N>& other) const;
|
||||
void addMultiplied(const Vec<N>& other, float x);
|
||||
void operator/= (float x);
|
||||
float& operator[] (int index);
|
||||
const float& operator[] (int index) const;
|
||||
void set(int index, float value);
|
||||
float lengthSquared() const;
|
||||
float length() const;
|
||||
void setLength(float len);
|
||||
void normalize();
|
||||
void print() const;
|
||||
static float distanceSquared(const Vec<N>& a, const Vec<N>& b);
|
||||
uint hash() const;
|
||||
void setHash(uint h) { hashVal = h; }
|
||||
private:
|
||||
float v[N];
|
||||
uint hashVal; // Only used for the constant input vectors, so we only need to calc once.
|
||||
uint64_t lololol; // Speeds up the average compression by a couple of seconds on my machine. Probably some alignment stuff.
|
||||
};
|
||||
|
||||
template<uint N>
|
||||
inline Vec<N>::Vec(const Vec<N> &other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::zero() {
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] = 0;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::operator= (const Vec<N>& other) {
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] = other.v[i];
|
||||
hashVal = other.hashVal;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline bool Vec<N>::operator== (const Vec<N>& other) const {
|
||||
for (uint i=0; i<N; ++i)
|
||||
if (fabs(v[i] - other.v[i]) > 0.001f)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline Vec<N> Vec<N>::operator+ (const Vec<N>& other) const {
|
||||
Vec<N> ret;
|
||||
for (uint i=0; i<N; ++i)
|
||||
ret.v[i] = v[i] + other.v[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline Vec<N> Vec<N>::operator- (const Vec<N>& other) const {
|
||||
Vec<N> ret;
|
||||
for (uint i=0; i<N; ++i)
|
||||
ret.v[i] = v[i] - other.v[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::addMultiplied(const Vec<N>& other, float x) {
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] += (other.v[i] * x);
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::operator+= (const Vec<N>& other) {
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] += other.v[i];
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::operator-= (const Vec<N>& other) {
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] -= other.v[i];
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::operator/= (float x) {
|
||||
const float invx = 1.0f / x;
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] *= invx;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline float& Vec<N>::operator[] (int index) {
|
||||
return v[index];
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline const float& Vec<N>::operator[] (int index) const {
|
||||
return v[index];
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::set(int index, float value) {
|
||||
v[index] = value;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline float Vec<N>::length() const {
|
||||
return sqrt(lengthSquared());
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline float Vec<N>::lengthSquared() const {
|
||||
float ret = 0;
|
||||
for (uint i=0; i<N; ++i)
|
||||
ret += (v[i] * v[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::setLength(float len) {
|
||||
float x = (1.0f / length()) * len;
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] *= x;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void Vec<N>::normalize() {
|
||||
const float invlen = 1.0f / length();
|
||||
for (uint i=0; i<N; ++i)
|
||||
v[i] *= invlen;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void Vec<N>::print() const {
|
||||
uf::stl::string str = "{ ";
|
||||
for (uint i=0; i<N; ++i) {
|
||||
str += std::to_string(v[i]);
|
||||
str += ' ';
|
||||
}
|
||||
str += '}';
|
||||
std::cout << str;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
float Vec<N>::distanceSquared(const Vec<N>& a, const Vec<N>& b) {
|
||||
return (a - b).lengthSquared();
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline uint Vec<N>::hash() const {
|
||||
return hashVal;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
uint qHash(const Vec<N>& vec) {
|
||||
return vec.hash();
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template<uint N>
|
||||
struct hash<Vec<N>> {
|
||||
size_t operator()(const Vec<N>& v) const {
|
||||
uint32_t h=2166136261u;
|
||||
for(uint i=0;i<N;i++){
|
||||
uint32_t bits;
|
||||
memcpy(&bits,&v[i],sizeof(float));
|
||||
h ^= bits;
|
||||
h *= 16777619u;
|
||||
}
|
||||
return h ^ v.hash();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
class VectorQuantizer {
|
||||
public:
|
||||
struct Code {
|
||||
Vec<N> codeVec;
|
||||
Vec<N> vecSum;
|
||||
int vecCount = 0;
|
||||
float maxDistance = 0;
|
||||
Vec<N> maxDistanceVec;
|
||||
};
|
||||
|
||||
uf::stl::vector<Code> codes;
|
||||
|
||||
int codeCount() const { return (int)codes.size(); }
|
||||
const Vec<N>& codeVector(int i) const { return codes[i].codeVec; }
|
||||
|
||||
int findClosest(const Vec<N>& vec) const;
|
||||
int findBestSplitCandidate() const;
|
||||
void removeUnusedCodes();
|
||||
void place(const uf::stl::unordered_map<Vec<N>,int>& vecs);
|
||||
void split();
|
||||
void splitCode(int index);
|
||||
void compress(const uf::stl::vector<Vec<N>>& vectors,int numCodes);
|
||||
bool writeReportToFile(const uf::stl::string& filename);
|
||||
};
|
||||
|
||||
inline uint32_t packColor(const RGBA& c) {
|
||||
return (uint32_t(c.a)<<24)|(uint32_t(c.r)<<16)|(uint32_t(c.g)<<8)|uint32_t(c.b);
|
||||
}
|
||||
inline RGBA unpackColor(uint32_t argb) {
|
||||
RGBA c;
|
||||
c.a = (argb>>24)&0xFF;
|
||||
c.r = (argb>>16)&0xFF;
|
||||
c.g = (argb>>8)&0xFF;
|
||||
c.b = argb&0xFF;
|
||||
return c;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void rgb2vec(uint32_t rgb, Vec<N>& vec, uint offset=0) {
|
||||
RGBA c = unpackColor(rgb);
|
||||
vec[offset+0] = c.r/255.f;
|
||||
vec[offset+1] = c.g/255.f;
|
||||
vec[offset+2] = c.b/255.f;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void argb2vec(uint32_t argb, Vec<N>& vec, uint offset=0) {
|
||||
RGBA c = unpackColor(argb);
|
||||
vec[offset+0] = c.a/255.f;
|
||||
vec[offset+1] = c.r/255.f;
|
||||
vec[offset+2] = c.g/255.f;
|
||||
vec[offset+3] = c.b/255.f;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void vec2rgb(const Vec<N>& vec, uint32_t& rgb, uint offset=0) {
|
||||
RGBA c;
|
||||
c.r = (uint8_t)(vec[offset+0]*255);
|
||||
c.g = (uint8_t)(vec[offset+1]*255);
|
||||
c.b = (uint8_t)(vec[offset+2]*255);
|
||||
c.a = 255;
|
||||
rgb = packColor(c);
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
inline void vec2argb(const Vec<N>& vec, uint32_t& argb, uint offset=0) {
|
||||
RGBA c;
|
||||
c.a = (uint8_t)(vec[offset+0]*255);
|
||||
c.r = (uint8_t)(vec[offset+1]*255);
|
||||
c.g = (uint8_t)(vec[offset+2]*255);
|
||||
c.b = (uint8_t)(vec[offset+3]*255);
|
||||
argb = packColor(c);
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
int VectorQuantizer<N>::findClosest(const Vec<N>& vec) const {
|
||||
if (codes.size() <= 1) return 0;
|
||||
int closestIndex = 0;
|
||||
float closestDist = Vec<N>::distanceSquared(codes[0].codeVec, vec);
|
||||
for(size_t i=1; i<codes.size(); i++) {
|
||||
float d = Vec<N>::distanceSquared(codes[i].codeVec, vec);
|
||||
if(d < closestDist){
|
||||
closestDist=d;
|
||||
closestIndex=(int)i;
|
||||
if (closestDist < 0.0001f) return closestIndex;
|
||||
}
|
||||
}
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
int VectorQuantizer<N>::findBestSplitCandidate() const {
|
||||
int idx=-1;
|
||||
float furthest=0;
|
||||
for(size_t i=0;i<codes.size();i++){
|
||||
if(codes[i].vecCount>1 && codes[i].maxDistance>furthest){
|
||||
furthest=codes[i].maxDistance;
|
||||
idx=(int)i;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void VectorQuantizer<N>::removeUnusedCodes() {
|
||||
size_t oldSize=codes.size();
|
||||
codes.erase(
|
||||
std::remove_if(codes.begin(), codes.end(),
|
||||
[](const Code& c){ return c.vecCount==0; }),
|
||||
codes.end()
|
||||
);
|
||||
if(codes.size()<oldSize){
|
||||
std::cout<<"Removed "<<(oldSize-codes.size())<<" unused codes\n";
|
||||
}
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void VectorQuantizer<N>::place(const uf::stl::unordered_map<Vec<N>,int>& vecs) {
|
||||
for(auto& code:codes){
|
||||
code.vecCount=0;
|
||||
code.vecSum.zero();
|
||||
code.maxDistance=0;
|
||||
code.maxDistanceVec.zero();
|
||||
}
|
||||
|
||||
for(const auto& kv:vecs){
|
||||
const Vec<N>& vec=kv.first;
|
||||
int count=kv.second;
|
||||
Code& code = codes[findClosest(vec)];
|
||||
|
||||
code.vecSum.addMultiplied(vec,count);
|
||||
code.vecCount+=count;
|
||||
|
||||
float dist=Vec<N>::distanceSquared(code.codeVec,vec);
|
||||
if(dist>code.maxDistance){
|
||||
code.maxDistance=dist;
|
||||
code.maxDistanceVec=vec;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& code:codes){
|
||||
if(code.vecCount>0){
|
||||
code.vecSum /= (float)code.vecCount;
|
||||
code.codeVec=code.vecSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void VectorQuantizer<N>::split() {
|
||||
int SIZE=(int)codes.size();
|
||||
for(int i=0;i<SIZE;i++){
|
||||
if(codes[i].vecCount>1){
|
||||
splitCode(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void VectorQuantizer<N>::splitCode(int index) {
|
||||
Code& code=codes[index];
|
||||
Vec<N> diff=code.maxDistanceVec-code.codeVec;
|
||||
diff.setLength(0.01f);
|
||||
Vec<N> newVec=code.codeVec;
|
||||
for(uint i=0;i<N;i++) newVec[i]+=diff[i];
|
||||
for(uint i=0;i<N;i++) code.codeVec[i]-=diff[i];
|
||||
Code newCode;
|
||||
newCode.codeVec=newVec;
|
||||
codes.push_back(newCode);
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
void VectorQuantizer<N>::compress(const uf::stl::vector<Vec<N>>& vectors,int numCodes) {
|
||||
using clock=std::chrono::steady_clock;
|
||||
auto start=clock::now();
|
||||
|
||||
uf::stl::unordered_map<Vec<N>,int> rle;
|
||||
for(const auto& v:vectors) rle[v]++;
|
||||
|
||||
std::cout<<"RLE result: "<<vectors.size()<<" => "<<rle.size()<<"\n";
|
||||
|
||||
codes.clear();
|
||||
codes.resize(1);
|
||||
codes.reserve(numCodes);
|
||||
place(rle);
|
||||
|
||||
int splits=0, repairs=0;
|
||||
while((int)(codes.size()*2)<=numCodes){
|
||||
size_t before=codes.size();
|
||||
split();
|
||||
place(rle); place(rle); place(rle);
|
||||
removeUnusedCodes();
|
||||
|
||||
if(codes.size()==before){
|
||||
std::cout<<"No further improvement by splitting\n";
|
||||
break;
|
||||
}
|
||||
splits++;
|
||||
std::cout<<"Split "<<splits<<" done. Codes: "<<codes.size()<<"\n";
|
||||
}
|
||||
|
||||
while((int)codes.size()<numCodes){
|
||||
size_t before=codes.size();
|
||||
int n=numCodes-before;
|
||||
for(int i=0;i<n;i++){
|
||||
int idx=findBestSplitCandidate();
|
||||
if(idx==-1) break;
|
||||
splitCode(idx);
|
||||
codes[idx].maxDistance=0;
|
||||
}
|
||||
if(codes.size()==before){
|
||||
std::cout<<"No further improvement by repairing\n";
|
||||
break;
|
||||
}
|
||||
place(rle); place(rle); place(rle);
|
||||
removeUnusedCodes();
|
||||
repairs++;
|
||||
std::cout<<"Repair "<<repairs<<" done. Codes: "<<codes.size()<<"\n";
|
||||
}
|
||||
auto ms=std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()-start).count();
|
||||
std::cout<<"Compression completed in "<<ms<<" ms\n";
|
||||
}
|
||||
|
||||
template<uint N>
|
||||
bool VectorQuantizer<N>::writeReportToFile(const uf::stl::string& fname){
|
||||
std::ofstream f(fname);
|
||||
if(!f.is_open()){
|
||||
std::cerr<<"Failed to open "<<fname<<"\n";
|
||||
return false;
|
||||
}
|
||||
for(int i=0;i<(int)codes.size();i++){
|
||||
f<<"Code: "<<i<<"\tUses: "<<codes[i].vecCount<<"\tError: "<<codes[i].maxDistance<<"\n";
|
||||
}
|
||||
return true;
|
||||
}
|
273
dep/src/texconv/common.cpp
Normal file
273
dep/src/texconv/common.cpp
Normal file
@ -0,0 +1,273 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include <texconv/common.h>
|
||||
#include <texconv/imagecontainer.h>
|
||||
|
||||
#define M_PI 3.1415926535897932384f
|
||||
#define HALF_PI M_PI/2.0f
|
||||
#define DOUBLE_PI M_PI*2.0f
|
||||
|
||||
static inline bool powerOfTwo(int x) {
|
||||
return (x > 0 && (x & (x - 1)) == 0);
|
||||
}
|
||||
inline uint8_t clamp255(int v) { return (uint8_t)(v < 0 ? 0 : (v > 255 ? 255 : v)); }
|
||||
|
||||
int nextPowerOfTwo(int x) {
|
||||
if (x <= 0) return 1;
|
||||
int pw2 = 1;
|
||||
while (pw2 < x) pw2 *= 2;
|
||||
return pw2;
|
||||
}
|
||||
|
||||
bool isValidSize(int width, int height, int textureType) {
|
||||
if (textureType & FLAG_STRIDED) {
|
||||
if (width < TEXTURE_STRIDE_MIN || width > TEXTURE_STRIDE_MAX || (width % 32) != 0)
|
||||
return false;
|
||||
if (height < TEXTURE_SIZE_MIN || height > TEXTURE_SIZE_MAX || !powerOfTwo(height))
|
||||
return false;
|
||||
} else {
|
||||
int minSize = (textureType & FLAG_MIPMAPPED) ? 1 : TEXTURE_SIZE_MIN;
|
||||
if (width < minSize || width > TEXTURE_SIZE_MAX || !powerOfTwo(width))
|
||||
return false;
|
||||
if (height < minSize || height > TEXTURE_SIZE_MAX || !powerOfTwo(height))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeZeroes(std::ostream& stream, int n) {
|
||||
char zero = 0;
|
||||
for (int i=0; i<n; i++) stream.write(&zero, 1);
|
||||
}
|
||||
|
||||
bool isFormat(int textureType, int pixelFormat) {
|
||||
return ((textureType >> PIXELFORMAT_SHIFT) & PIXELFORMAT_MASK) == pixelFormat;
|
||||
}
|
||||
bool isPaletted(int textureType) {
|
||||
return isFormat(textureType, PIXELFORMAT_PAL4BPP) || isFormat(textureType, PIXELFORMAT_PAL8BPP);
|
||||
}
|
||||
bool is16BPP(int textureType) { return !isPaletted(textureType); }
|
||||
|
||||
static uint16_t toSpherical(const RGBA& c) {
|
||||
|
||||
float vx = (c.r/255.0f) * 2.0f - 1.0f;
|
||||
float vy = (c.g/255.0f) * 2.0f - 1.0f;
|
||||
float vz = (c.b/255.0f);
|
||||
|
||||
float radius = std::sqrt(vx*vx + vy*vy + vz*vz);
|
||||
if (radius < 1e-6f) radius = 1e-6f;
|
||||
|
||||
float polar = std::acos(vz / radius);
|
||||
float azimuth = std::atan2(vy, vx);
|
||||
|
||||
polar = (HALF_PI - polar) / (HALF_PI) * 255.0f;
|
||||
int S = std::max(0, std::min(255, (int)polar));
|
||||
|
||||
if (azimuth < 0) azimuth += 2*M_PI;
|
||||
azimuth = azimuth / (2*M_PI) * 255.0f;
|
||||
int R = std::max(0, std::min(255, (int)azimuth));
|
||||
|
||||
return (uint16_t)((S << 8) | R);
|
||||
}
|
||||
|
||||
static RGBA toCartesian(uint16_t SR) {
|
||||
float S = (1.0 - ((SR >> 8) / 255.0)) * HALF_PI;
|
||||
float R = ((SR & 0xFF) / 255.0) * DOUBLE_PI;
|
||||
if (R > M_PI) R -= DOUBLE_PI;
|
||||
RGBA color;
|
||||
color.r = (sin(S) * cos(R) + 1.0f) * 0.5f;
|
||||
color.g = (sin(S) * sin(R) + 1.0f) * 0.5f;
|
||||
color.b = (cos(S) + 1.0f) * 0.5f;
|
||||
color.a = 1;
|
||||
return color;
|
||||
}
|
||||
|
||||
uint16_t to16BPP(const RGBA& argb, int pixelFormat) {
|
||||
uint16_t a,r,g,b;
|
||||
switch (pixelFormat) {
|
||||
case PIXELFORMAT_ARGB1555:
|
||||
a = (argb.a < 128) ? 0 : 1;
|
||||
r = (argb.r >> 3) & 0x1F;
|
||||
g = (argb.g >> 3) & 0x1F;
|
||||
b = (argb.b >> 3) & 0x1F;
|
||||
return (a<<15)|(r<<10)|(g<<5)|b;
|
||||
case PIXELFORMAT_RGB565:
|
||||
r = (argb.r >> 3) & 0x1F;
|
||||
g = (argb.g >> 2) & 0x3F;
|
||||
b = (argb.b >> 3) & 0x1F;
|
||||
return (r<<11)|(g<<5)|b;
|
||||
case PIXELFORMAT_ARGB4444:
|
||||
a = (argb.a >> 4) & 0xF;
|
||||
r = (argb.r >> 4) & 0xF;
|
||||
g = (argb.g >> 4) & 0xF;
|
||||
b = (argb.b >> 4) & 0xF;
|
||||
return (a<<12)|(r<<8)|(g<<4)|b;
|
||||
case PIXELFORMAT_BUMPMAP:
|
||||
return toSpherical(argb);
|
||||
default:
|
||||
std::cerr << "Unsupported format " << pixelFormat << " in to16BPP\n";
|
||||
return 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
RGBA to32BPP(uint16_t px, int pixelFormat) {
|
||||
RGBA out{255,255,255,255};
|
||||
switch(pixelFormat) {
|
||||
case PIXELFORMAT_ARGB1555:
|
||||
out.a = (px>>15)&1 ? 255:0;
|
||||
out.r = ((px>>10)&0x1F)<<3;
|
||||
out.g = ((px>>5)&0x1F)<<3;
|
||||
out.b = ((px>>0)&0x1F)<<3;
|
||||
break;
|
||||
case PIXELFORMAT_RGB565:
|
||||
out.r = ((px>>11)&0x1F)<<3;
|
||||
out.g = ((px>>5)&0x3F)<<2;
|
||||
out.b = ((px>>0)&0x1F)<<3;
|
||||
out.a = 255;
|
||||
break;
|
||||
case PIXELFORMAT_ARGB4444:
|
||||
out.a = ((px>>12)&0xF)<<4;
|
||||
out.r = ((px>>8)&0xF)<<4;
|
||||
out.g = ((px>>4)&0xF)<<4;
|
||||
out.b = ((px>>0)&0xF)<<4;
|
||||
break;
|
||||
case PIXELFORMAT_BUMPMAP:
|
||||
return toCartesian(px);
|
||||
default: break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void RGBtoYUV422(const RGBA& c1, const RGBA& c2, uint16_t& yuv1, uint16_t& yuv2) {
|
||||
int avgR = (c1.r + c2.r)/2;
|
||||
int avgG = (c1.g + c2.g)/2;
|
||||
int avgB = (c1.b + c2.b)/2;
|
||||
|
||||
int Y0 = clamp255((int)(0.299*c1.r + 0.587*c1.g + 0.114*c1.b));
|
||||
int Y1 = clamp255((int)(0.299*c2.r + 0.587*c2.g + 0.114*c2.b));
|
||||
|
||||
int U = clamp255((int)(-0.169*avgR -0.331*avgG +0.499*avgB +128));
|
||||
int V = clamp255((int)(0.499*avgR -0.418*avgG -0.0813*avgB +128));
|
||||
|
||||
yuv1 = ((uint16_t)Y0<<8) | (uint16_t)U;
|
||||
yuv2 = ((uint16_t)Y1<<8) | (uint16_t)V;
|
||||
}
|
||||
|
||||
void YUV422toRGB(const uint16_t yuv1, const uint16_t yuv2, RGBA& rgb1, RGBA& rgb2) {
|
||||
int Y0 = (yuv1>>8)&0xFF;
|
||||
int Y1 = (yuv2>>8)&0xFF;
|
||||
int U = (yuv1&0xFF) -128;
|
||||
int V = (yuv2&0xFF) -128;
|
||||
|
||||
rgb1.r = clamp255((int)(Y0 + 1.375*V));
|
||||
rgb1.g = clamp255((int)(Y0 - 0.34375*U - 0.6875*V));
|
||||
rgb1.b = clamp255((int)(Y0 + 1.71875*U));
|
||||
rgb1.a = 255;
|
||||
|
||||
rgb2.r = clamp255((int)(Y1 + 1.375*V));
|
||||
rgb2.g = clamp255((int)(Y1 - 0.34375*U - 0.6875*V));
|
||||
rgb2.b = clamp255((int)(Y1 + 1.71875*U));
|
||||
rgb2.a = 255;
|
||||
}
|
||||
|
||||
|
||||
static int getPixelCount(int w,int h,int minw,int minh) {
|
||||
if (w<minw || h<minh) return 0;
|
||||
return w*h + getPixelCount(w/2,h/2,minw,minh);
|
||||
}
|
||||
|
||||
int calculateSize(int w, int h, int textureType) {
|
||||
const bool mipmapped = (textureType & FLAG_MIPMAPPED);
|
||||
const bool compressed = (textureType & FLAG_COMPRESSED);
|
||||
int bytes = 0;
|
||||
|
||||
if (mipmapped) {
|
||||
if (compressed) {
|
||||
bytes += 2048;
|
||||
bytes += 1;
|
||||
if (is16BPP(textureType)) {
|
||||
|
||||
bytes += getPixelCount(w,h,2,2) / 4;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL4BPP)) {
|
||||
|
||||
bytes += getPixelCount(w,h,4,4) / 16;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL8BPP)) {
|
||||
|
||||
bytes += getPixelCount(w,h,4,4) / 8;
|
||||
}
|
||||
} else {
|
||||
const int pixels = getPixelCount(w,h,1,1);
|
||||
if (is16BPP(textureType)) {
|
||||
bytes += MIPMAP_OFFSET_16BPP;
|
||||
bytes += pixels * 2;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL4BPP)) {
|
||||
bytes += MIPMAP_OFFSET_4BPP;
|
||||
bytes += 1;
|
||||
bytes += (pixels - 1) / 2;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL8BPP)) {
|
||||
bytes += MIPMAP_OFFSET_8BPP;
|
||||
bytes += pixels;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const int pixels = getPixelCount(w,h,w,h);
|
||||
if (compressed) {
|
||||
bytes += 2048;
|
||||
if (is16BPP(textureType)) {
|
||||
bytes += pixels / 4;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL4BPP)) {
|
||||
bytes += pixels / 16;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL8BPP)) {
|
||||
bytes += pixels / 8;
|
||||
}
|
||||
} else {
|
||||
if (is16BPP(textureType)) {
|
||||
bytes += pixels * 2;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL4BPP)) {
|
||||
bytes += pixels / 2;
|
||||
} else if (isFormat(textureType, PIXELFORMAT_PAL8BPP)) {
|
||||
bytes += pixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (bytes % 32 == 0) {
|
||||
return bytes;
|
||||
} else {
|
||||
return ((bytes / 32) + 1) * 32;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int writeTextureHeader(std::ostream& stream,int width,int height,int textureType){
|
||||
int size = calculateSize(width,height,textureType);
|
||||
if (textureType & FLAG_STRIDED) {
|
||||
width = nextPowerOfTwo(width);
|
||||
}
|
||||
|
||||
stream.write(TEXTURE_MAGIC,4);
|
||||
int16_t w16=(int16_t)width;
|
||||
int16_t h16=(int16_t)height;
|
||||
int32_t typ = textureType;
|
||||
int32_t sz = size;
|
||||
stream.write((char*)&w16,2);
|
||||
stream.write((char*)&h16,2);
|
||||
stream.write((char*)&typ,4);
|
||||
stream.write((char*)&sz,4);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t combineHash(const RGBA& rgba, uint32_t seed) {
|
||||
uint32_t val = ((rgba.a<<24)|(rgba.r<<16)|(rgba.g<<8)|rgba.b);
|
||||
seed ^= val + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||
return seed;
|
||||
}
|
||||
|
||||
#endif
|
280
dep/src/texconv/conv16bpp.cpp
Normal file
280
dep/src/texconv/conv16bpp.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <texconv/imagecontainer.h>
|
||||
#include <texconv/twiddler.h>
|
||||
#include <texconv/vqtools.h>
|
||||
#include <texconv/common.h>
|
||||
|
||||
static void convertAndWriteTexel(std::ostream& stream, const RGBA& texel, int pixelFormat, bool twiddled);
|
||||
static void writeStrideData(std::ostream& stream, const Image& img, int pixelFormat);
|
||||
static void writeUncompressedData(std::ostream& stream, const ImageContainer& images, int pixelFormat);
|
||||
static void writeCompressedData(std::ostream& stream, const ImageContainer& images, int pixelFormat);
|
||||
|
||||
void convert16BPP(std::ostream& stream, const ImageContainer& images, int textureType) {
|
||||
const int pixelFormat = (textureType >> PIXELFORMAT_SHIFT) & PIXELFORMAT_MASK;
|
||||
|
||||
if (textureType & FLAG_STRIDED) {
|
||||
writeStrideData(stream, images.getByIndex(0), pixelFormat);
|
||||
} else if (textureType & FLAG_COMPRESSED) {
|
||||
writeCompressedData(stream, images, pixelFormat);
|
||||
} else {
|
||||
writeUncompressedData(stream, images, pixelFormat);
|
||||
}
|
||||
}
|
||||
|
||||
static void convertAndWriteTexel(std::ostream& stream, const RGBA& texel, int pixelFormat, bool twiddled) {
|
||||
if (pixelFormat == PIXELFORMAT_YUV422) {
|
||||
static int index = 0;
|
||||
static RGBA savedTexel[3];
|
||||
|
||||
if (!twiddled && index == 1) {
|
||||
uint16_t yuv[2];
|
||||
RGBtoYUV422(savedTexel[0], texel, yuv[0], yuv[1]);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[0]), 2);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[1]), 2);
|
||||
index = 0;
|
||||
} else if (twiddled && index == 3) {
|
||||
uint16_t yuv[4];
|
||||
RGBtoYUV422(savedTexel[0], savedTexel[2], yuv[0], yuv[2]);
|
||||
RGBtoYUV422(savedTexel[1], texel, yuv[1], yuv[3]);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[0]), 2);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[1]), 2);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[2]), 2);
|
||||
stream.write(reinterpret_cast<char*>(&yuv[3]), 2);
|
||||
index = 0;
|
||||
} else {
|
||||
savedTexel[index] = texel;
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
uint16_t val = to16BPP(texel, pixelFormat);
|
||||
stream.write(reinterpret_cast<char*>(&val), 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void writeStrideData(std::ostream& stream, const Image& img, int pixelFormat) {
|
||||
for (int y=0; y<img.height(); y++) {
|
||||
for (int x=0; x<img.width(); x++) {
|
||||
convertAndWriteTexel(stream, img.pixel(x,y), pixelFormat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void writeUncompressedData(std::ostream& stream, const ImageContainer& images, int pixelFormat) {
|
||||
|
||||
if (images.hasMipmaps()) {
|
||||
writeZeroes(stream, MIPMAP_OFFSET_16BPP);
|
||||
}
|
||||
|
||||
|
||||
for (int i=0; i<images.imageCount(); i++) {
|
||||
const Image& img = images.getByIndex(i);
|
||||
|
||||
|
||||
if (img.width()==1 && img.height()==1 && pixelFormat == PIXELFORMAT_YUV422) {
|
||||
convertAndWriteTexel(stream, img.pixel(0,0), PIXELFORMAT_RGB565, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
Twiddler twiddler(img.width(), img.height());
|
||||
int pixels = img.width() * img.height();
|
||||
|
||||
|
||||
for (int j=0;j<pixels;j++) {
|
||||
int index = twiddler.index(j);
|
||||
int x = index % img.width();
|
||||
int y = index / img.width();
|
||||
convertAndWriteTexel(stream, img.pixel(x,y), pixelFormat, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static uint64_t packQuad(const RGBA& tl, const RGBA& tr,
|
||||
const RGBA& bl, const RGBA& br, int pixelFormat) {
|
||||
uint64_t a,b,c,d;
|
||||
if (pixelFormat == PIXELFORMAT_YUV422) {
|
||||
uint16_t yuv[4];
|
||||
RGBtoYUV422(tl,tr,yuv[0],yuv[1]);
|
||||
RGBtoYUV422(bl,br,yuv[2],yuv[3]);
|
||||
a=yuv[0]; b=yuv[1]; c=yuv[2]; d=yuv[3];
|
||||
} else {
|
||||
a=to16BPP(tl,pixelFormat);
|
||||
b=to16BPP(tr,pixelFormat);
|
||||
c=to16BPP(bl,pixelFormat);
|
||||
d=to16BPP(br,pixelFormat);
|
||||
}
|
||||
return (a<<48)|(b<<32)|(c<<16)|d;
|
||||
}
|
||||
|
||||
|
||||
static int encodeLossless(const ImageContainer& images,
|
||||
int pixelFormat,
|
||||
uf::stl::vector<Image>& indexedImages,
|
||||
uf::stl::vector<uint64_t>& codebook,
|
||||
int maxCodes) {
|
||||
uf::stl::unordered_map<uint64_t,int> uniqueQuads;
|
||||
|
||||
for (int i=0;i<images.imageCount();i++) {
|
||||
const Image& img=images.getByIndex(i);
|
||||
|
||||
if (img.width()<MIN_MIPMAP_VQ || img.height()<MIN_MIPMAP_VQ)
|
||||
continue;
|
||||
|
||||
Image indexed(img.width()/2, img.height()/2);
|
||||
indexed.allocateIndexed(256);
|
||||
|
||||
for (int y=0;y<img.height();y+=2) {
|
||||
for (int x=0;x<img.width();x+=2) {
|
||||
uint64_t quad = packQuad(img.pixel(x,y),
|
||||
img.pixel(x+1,y),
|
||||
img.pixel(x,y+1),
|
||||
img.pixel(x+1,y+1),
|
||||
pixelFormat);
|
||||
if (uniqueQuads.find(quad)==uniqueQuads.end())
|
||||
uniqueQuads[quad]=(int)uniqueQuads.size();
|
||||
|
||||
if ((int)uniqueQuads.size()<=maxCodes)
|
||||
indexed.setIndexedPixel(x/2,y/2,uniqueQuads[quad]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)uniqueQuads.size()<=maxCodes)
|
||||
indexedImages.push_back(indexed);
|
||||
}
|
||||
|
||||
if ((int)uniqueQuads.size()<=maxCodes) {
|
||||
codebook.resize(uniqueQuads.size());
|
||||
for (auto& kv:uniqueQuads) codebook[kv.second]=kv.first;
|
||||
} else {
|
||||
indexedImages.clear();
|
||||
}
|
||||
|
||||
return (int)uniqueQuads.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void writeCompressedData(std::ostream& stream, const ImageContainer& images, int pixelFormat) {
|
||||
uf::stl::vector<Image> indexedImages;
|
||||
uf::stl::vector<uint64_t> codebook;
|
||||
|
||||
int numQuads = encodeLossless(images, pixelFormat, indexedImages, codebook, 256);
|
||||
std::cout << "Source images contain " << numQuads << " unique quads\n";
|
||||
|
||||
if (numQuads > 256) {
|
||||
if ((pixelFormat != PIXELFORMAT_ARGB1555) && (pixelFormat != PIXELFORMAT_ARGB4444)) {
|
||||
|
||||
uf::stl::vector<Vec<12>> vectors;
|
||||
VectorQuantizer<12> vq;
|
||||
|
||||
for (int i=0; i<images.imageCount(); i++) {
|
||||
const Image& img=images.getByIndex(i);
|
||||
if (img.width()<MIN_MIPMAP_VQ || img.height()<MIN_MIPMAP_VQ) continue;
|
||||
|
||||
for (int y=0;y<img.height();y+=2) {
|
||||
for(int x=0;x<img.width();x+=2) {
|
||||
Vec<12> vec;
|
||||
int offset=0;
|
||||
for(int yy=0;yy<2;yy++){
|
||||
for(int xx=0;xx<2;xx++){
|
||||
RGBA px=img.pixel(x+xx,y+yy);
|
||||
vec[offset+0]=px.r/255.0f;
|
||||
vec[offset+1]=px.g/255.0f;
|
||||
vec[offset+2]=px.b/255.0f;
|
||||
offset+=3;
|
||||
}
|
||||
}
|
||||
vectors.push_back(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
vq.compress(vectors,256);
|
||||
|
||||
|
||||
for (int i=0;i<vq.codeCount();i++) {
|
||||
const Vec<12>& vec=vq.codeVector(i);
|
||||
RGBA tl{(uint8_t)(vec[0]*255),(uint8_t)(vec[1]*255),(uint8_t)(vec[2]*255),255};
|
||||
RGBA tr{(uint8_t)(vec[3]*255),(uint8_t)(vec[4]*255),(uint8_t)(vec[5]*255),255};
|
||||
RGBA bl{(uint8_t)(vec[6]*255),(uint8_t)(vec[7]*255),(uint8_t)(vec[8]*255),255};
|
||||
RGBA br{(uint8_t)(vec[9]*255),(uint8_t)(vec[10]*255),(uint8_t)(vec[11]*255),255};
|
||||
codebook.push_back(packQuad(tl,tr,bl,br,pixelFormat));
|
||||
}
|
||||
|
||||
|
||||
for (int i=0; i<images.imageCount(); i++) {
|
||||
const Image& src=images.getByIndex(i);
|
||||
if (src.width()<MIN_MIPMAP_VQ || src.height()<MIN_MIPMAP_VQ) continue;
|
||||
Image idx(src.width()/2, src.height()/2);
|
||||
idx.allocateIndexed(256);
|
||||
for(int y=0;y<src.height();y+=2){
|
||||
for(int x=0;x<src.width();x+=2){
|
||||
Vec<12> v;
|
||||
int off=0;
|
||||
for(int yy=0;yy<2;yy++)for(int xx=0;xx<2;xx++){
|
||||
RGBA p=src.pixel(x+xx,y+yy);
|
||||
v[off+0]=p.r/255.0f;
|
||||
v[off+1]=p.g/255.0f;
|
||||
v[off+2]=p.b/255.0f;
|
||||
off+=3;
|
||||
}
|
||||
int codeIdx=vq.findClosest(v);
|
||||
idx.setIndexedPixel(x/2,y/2,(uint8_t)codeIdx);
|
||||
}
|
||||
}
|
||||
indexedImages.push_back(idx);
|
||||
}
|
||||
} else {
|
||||
|
||||
std::cerr<<"ARGB VQ compression not yet implemented!\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t codes[256*4];
|
||||
memset(codes,0,sizeof(codes));
|
||||
for (int i=0;i<(int)codebook.size();i++) {
|
||||
uint64_t quad=codebook[i];
|
||||
codes[i*4+0]=(uint16_t)((quad>>48)&0xFFFF);
|
||||
codes[i*4+1]=(uint16_t)((quad>>32)&0xFFFF);
|
||||
codes[i*4+2]=(uint16_t)((quad>>16)&0xFFFF);
|
||||
codes[i*4+3]=(uint16_t)((quad>> 0)&0xFFFF);
|
||||
}
|
||||
|
||||
|
||||
for(int i=0;i<1024;i++){
|
||||
stream.write((char*)&codes[i],2);
|
||||
}
|
||||
|
||||
|
||||
if(images.imageCount()>1)
|
||||
writeZeroes(stream,1);
|
||||
|
||||
|
||||
for(const auto& img:indexedImages){
|
||||
Twiddler twiddler(img.width(),img.height());
|
||||
int pixels=img.width()*img.height();
|
||||
for(int j=0;j<pixels;j++){
|
||||
int idx=twiddler.index(j);
|
||||
uint8_t val=img.indexedPixelAt(idx%img.width(),idx/img.width());
|
||||
stream.write((char*)&val,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
387
dep/src/texconv/convpal.cpp
Normal file
387
dep/src/texconv/convpal.cpp
Normal file
@ -0,0 +1,387 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <texconv/imagecontainer.h>
|
||||
#include <texconv/twiddler.h>
|
||||
#include <texconv/palette.h>
|
||||
#include <texconv/vqtools.h>
|
||||
#include <texconv/common.h>
|
||||
|
||||
static void vectorizeARGB(const ImageContainer& images, uf::stl::vector<Vec<4>>& vectors) {
|
||||
for (int i=0;i<images.imageCount();i++) {
|
||||
const Image& img=images.getByIndex(i);
|
||||
for (int y=0;y<img.height();y++) {
|
||||
for (int x=0;x<img.width();x++) {
|
||||
RGBA px=img.pixel(x,y);
|
||||
Vec<4> vec;
|
||||
vec[0]=px.a/255.f;
|
||||
vec[1]=px.r/255.f;
|
||||
vec[2]=px.g/255.f;
|
||||
vec[3]=px.b/255.f;
|
||||
vectors.push_back(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void devectorizeARGB(const ImageContainer& srcImages, const uf::stl::vector<Vec<4>>& vectors,
|
||||
const VectorQuantizer<4>& vq, uf::stl::vector<Image>& indexedImages, Palette& palette) {
|
||||
int vindex=0;
|
||||
for (int i=0;i<srcImages.imageCount();i++) {
|
||||
const Image& src=srcImages.getByIndex(i);
|
||||
Image dst(src.width(),src.height());
|
||||
dst.allocateIndexed(256);
|
||||
|
||||
for (int y=0;y<src.height();y++) {
|
||||
for (int x=0;x<src.width();x++) {
|
||||
const Vec<4>& vec=vectors[vindex++];
|
||||
int codeIndex=vq.findClosest(vec);
|
||||
dst.setIndexedPixel(x,y,(uint8_t)codeIndex);
|
||||
}
|
||||
}
|
||||
indexedImages.push_back(dst);
|
||||
}
|
||||
|
||||
for (int i=0;i<vq.codeCount();i++) {
|
||||
const Vec<4>& v=vq.codeVector(i);
|
||||
uint32_t color=(uint8_t)(v[0]*255)<<24 | (uint8_t)(v[1]*255)<<16 |
|
||||
(uint8_t)(v[2]*255)<<8 | (uint8_t)(v[3]*255);
|
||||
palette.insert(color);
|
||||
}
|
||||
}
|
||||
|
||||
void convertToIndexedImages(const ImageContainer& src, const Palette& pal, uf::stl::vector<Image>& dst);
|
||||
void writeUncompressed4BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages);
|
||||
void writeUncompressed8BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages);
|
||||
void writeUncompressedPreview(const uf::stl::string& filename, const uf::stl::vector<Image>& indexedImages, const Palette& palette);
|
||||
void writeCompressed4BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages, const Palette& palette);
|
||||
void writeCompressed8BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages, const Palette& palette);
|
||||
|
||||
/*
|
||||
* This conversion basically has three modes:
|
||||
*
|
||||
* 1. The source images contain <= unique colors than the requested mode
|
||||
* needs, so conversion will be quick and lossless.
|
||||
*
|
||||
* 2. The source images contain > unique colors than the requested mode
|
||||
* needs. In this case we utilize vector quantization to reduce the
|
||||
* color count.
|
||||
*
|
||||
* 3. The user has requested for the image to be compressed. This is a two
|
||||
* stage process. First, reduce the input images to the color count needed.
|
||||
* Then, using the reduced images as input, perform vector quantization
|
||||
* with a vector dimension of 32 or 64 (2x4 or 4x4 pixel blocks).
|
||||
*/
|
||||
|
||||
void convertPaletted(std::ostream& stream, const ImageContainer& images, int textureType, const uf::stl::string& palFilename) {
|
||||
const int maxColors = isFormat(textureType, PIXELFORMAT_PAL4BPP) ? 16 : 256;
|
||||
Palette palette(images);
|
||||
uf::stl::vector<Image> indexedImages;
|
||||
|
||||
std::cout<<"Palette contains "<<palette.colorCount()<<" colors\n";
|
||||
if (palette.colorCount()>maxColors) {
|
||||
std::cout<<"Reducing palette to "<<maxColors<<" colors\n";
|
||||
palette.clear();
|
||||
VectorQuantizer<4> vq;
|
||||
uf::stl::vector<Vec<4>> vectors;
|
||||
vectorizeARGB(images,vectors);
|
||||
vq.compress(vectors,maxColors);
|
||||
devectorizeARGB(images,vectors,vq,indexedImages,palette);
|
||||
} else {
|
||||
|
||||
for (int i=0;i<images.imageCount();i++) {
|
||||
const Image& img=images.getByIndex(i);
|
||||
Image idx(img.width(),img.height());
|
||||
idx.allocateIndexed(maxColors);
|
||||
for (int y=0;y<img.height();y++)
|
||||
for (int x=0;x<img.width();x++) {
|
||||
uint32_t color=(img.pixel(x,y).a<<24)|(img.pixel(x,y).r<<16)|(img.pixel(x,y).g<<8)|img.pixel(x,y).b;
|
||||
idx.setIndexedPixel(x,y,(uint8_t)palette.indexOf(color));
|
||||
}
|
||||
indexedImages.push_back(idx);
|
||||
}
|
||||
}
|
||||
|
||||
palette.save(palFilename);
|
||||
|
||||
if (textureType & FLAG_COMPRESSED) {
|
||||
if (isFormat(textureType, PIXELFORMAT_PAL4BPP))
|
||||
writeCompressed4BPPData(stream, indexedImages, palette);
|
||||
if (isFormat(textureType, PIXELFORMAT_PAL8BPP))
|
||||
writeCompressed8BPPData(stream, indexedImages, palette);
|
||||
} else {
|
||||
if (isFormat(textureType, PIXELFORMAT_PAL4BPP))
|
||||
writeUncompressed4BPPData(stream, indexedImages);
|
||||
if (isFormat(textureType, PIXELFORMAT_PAL8BPP))
|
||||
writeUncompressed8BPPData(stream, indexedImages);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void convertToIndexedImages(const ImageContainer& src, const Palette& pal, uf::stl::vector<Image>& dst) {
|
||||
dst.clear();
|
||||
for (int i=0; i<src.imageCount(); i++) {
|
||||
const Image& img = src.getByIndex(i);
|
||||
Image dstImg(img.width(), img.height());
|
||||
dstImg.allocateIndexed(pal.colorCount());
|
||||
|
||||
for (int y=0; y<img.height(); y++) {
|
||||
for (int x=0; x<img.width(); x++) {
|
||||
RGBA px = img.pixel(x,y);
|
||||
uint32_t argb = (uint32_t(px.a)<<24)|(uint32_t(px.r)<<16)|(uint32_t(px.g)<<8)|(uint32_t)px.b;
|
||||
uint8_t index = (uint8_t)pal.indexOf(argb);
|
||||
dstImg.setIndexedPixel(x,y,index);
|
||||
}
|
||||
}
|
||||
dst.push_back(dstImg);
|
||||
}
|
||||
}
|
||||
|
||||
void writeUncompressed4BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages) {
|
||||
if (indexedImages.size() > 1) {
|
||||
writeZeroes(stream, MIPMAP_OFFSET_4BPP);
|
||||
}
|
||||
for (size_t i=0;i<indexedImages.size();i++) {
|
||||
const Image& img=indexedImages[i];
|
||||
|
||||
|
||||
if (img.width()==1) {
|
||||
uint8_t val=img.indexedPixelAt(0,0);
|
||||
stream.write((char*)&val,1);
|
||||
continue;
|
||||
}
|
||||
|
||||
Twiddler twiddler(img.width(),img.height());
|
||||
int pixels=img.width()*img.height();
|
||||
for (int j=0;j<pixels;j+=2) {
|
||||
uint8_t vals[2];
|
||||
for(int k=0;k<2;k++){
|
||||
int tIdx=twiddler.index(j+k);
|
||||
int x=tIdx%img.width();
|
||||
int y=tIdx/img.width();
|
||||
vals[k]=img.indexedPixelAt(x,y)&0xF;
|
||||
}
|
||||
uint8_t packed=(vals[1]<<4)|(vals[0]);
|
||||
stream.write((char*)&packed,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeUncompressed8BPPData(std::ostream& stream, const uf::stl::vector<Image>& indexedImages) {
|
||||
if (indexedImages.size() > 1) {
|
||||
writeZeroes(stream, MIPMAP_OFFSET_8BPP);
|
||||
}
|
||||
for (size_t i=0;i<indexedImages.size();i++) {
|
||||
const Image& img=indexedImages[i];
|
||||
Twiddler twiddler(img.width(),img.height());
|
||||
int pixels=img.width()*img.height();
|
||||
for (int j=0;j<pixels;j++) {
|
||||
int tIdx=twiddler.index(j);
|
||||
int x=tIdx%img.width();
|
||||
int y=tIdx/img.width();
|
||||
uint8_t val=img.indexedPixelAt(x,y);
|
||||
stream.write((char*)&val,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define STORE_FULL 0
|
||||
#define STORE_LEFT 1
|
||||
#define STORE_RIGHT 2
|
||||
|
||||
template<uint N>
|
||||
static void grab2x4Block(const Image& img,const Palette& pal,int x,int y,Vec<N>& vec,int storeMethod){
|
||||
static const int indexLUT[3][8]={
|
||||
{0,4,8,12,16,20,24,28},
|
||||
{0,4,16,20,32,36,48,52},
|
||||
{8,12,24,28,40,44,56,60}
|
||||
};
|
||||
int idx=0;
|
||||
uint32_t seed=vec.hash();
|
||||
for(int yy=y;yy<y+4;yy++){
|
||||
for(int xx=x;xx<x+2;xx++){
|
||||
uint32_t color=pal.colorAt(img.indexedPixelAt(xx,yy));
|
||||
RGBA c{(uint8_t)((color>>16)&0xFF),(uint8_t)((color>>8)&0xFF),(uint8_t)(color&0xFF),(uint8_t)((color>>24)&0xFF)};
|
||||
vec[indexLUT[storeMethod][idx]+0]=c.a/255.f;
|
||||
vec[indexLUT[storeMethod][idx]+1]=c.r/255.f;
|
||||
vec[indexLUT[storeMethod][idx]+2]=c.g/255.f;
|
||||
vec[indexLUT[storeMethod][idx]+3]=c.b/255.f;
|
||||
seed=combineHash(c,seed);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
vec.setHash(seed);
|
||||
}
|
||||
|
||||
static void vectorizePalette(const Palette& pal, uf::stl::vector<Vec<4>>& vectors) {
|
||||
for (int i=0; i<pal.colorCount(); i++) {
|
||||
Vec<4> vec;
|
||||
argb2vec(pal.colorAt(i), vec);
|
||||
vectors.push_back(vec);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t findClosest(const uf::stl::vector<Vec<4>>& vectors, const Vec<4>& vec) {
|
||||
uint8_t closestIndex = 0;
|
||||
float closestDistance = Vec<4>::distanceSquared(vectors[0], vec);
|
||||
for (int i=1; i<vectors.size(); i++) {
|
||||
float distance = Vec<4>::distanceSquared(vectors[i], vec);
|
||||
if (distance < closestDistance) {
|
||||
closestIndex = (uint8_t)i;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
void writeCompressed4BPPData(std::ostream& stream,const uf::stl::vector<Image>& indexedImages,const Palette& palette){
|
||||
VectorQuantizer<64> vq;
|
||||
uf::stl::vector<Vec<64>> vectors;
|
||||
|
||||
if(indexedImages.size()>1){
|
||||
Vec<64> vec; vec.zero();
|
||||
for(size_t i=0;i<indexedImages.size();i++){
|
||||
const Image& img=indexedImages[i];
|
||||
if(img.width()<MIN_MIPMAP_PALVQ||img.height()<MIN_MIPMAP_PALVQ) continue;
|
||||
int blocks=(img.width()*img.height())/16;
|
||||
Twiddler twiddler(img.width()/4,img.height()/4);
|
||||
for(int j=0;j<blocks;j++){
|
||||
int tw=twiddler.index(j);
|
||||
int x=(tw%(img.width()/4))*4;
|
||||
int y=(tw/(img.width()/4))*4;
|
||||
if(vectors.empty()){
|
||||
grab2x4Block(img,palette,x,y,vec,STORE_LEFT);
|
||||
}
|
||||
grab2x4Block(img,palette,x,y,vec,STORE_RIGHT);
|
||||
vectors.push_back(vec);
|
||||
vec.setHash(0);
|
||||
grab2x4Block(img,palette,x+2,y,vec,STORE_LEFT);
|
||||
if(i==indexedImages.size()-1 && j==blocks-1){
|
||||
grab2x4Block(img,palette,x+2,y,vec,STORE_RIGHT);
|
||||
vectors.push_back(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
const Image& img=indexedImages[0];
|
||||
int blocks=(img.width()*img.height())/16;
|
||||
Twiddler twiddler(img.width()/4,img.height()/4);
|
||||
for(int j=0;j<blocks;j++){
|
||||
int tw=twiddler.index(j);
|
||||
int x=(tw%(img.width()/4))*4;
|
||||
int y=(tw/(img.width()/4))*4;
|
||||
Vec<64> vec; vec.zero();
|
||||
grab2x4Block(img,palette,x,y,vec,STORE_LEFT);
|
||||
grab2x4Block(img,palette,x+2,y,vec,STORE_RIGHT);
|
||||
vectors.push_back(vec);
|
||||
}
|
||||
}
|
||||
|
||||
vq.compress(vectors,256);
|
||||
|
||||
|
||||
uf::stl::vector<Vec<4>> paletteVecs;
|
||||
for(int i=0;i<palette.colorCount();i++){
|
||||
Vec<4> v; v[0]=((palette.colorAt(i)>>24)&0xFF)/255.f;
|
||||
v[1]=((palette.colorAt(i)>>16)&0xFF)/255.f;
|
||||
v[2]=((palette.colorAt(i)>>8)&0xFF)/255.f;
|
||||
v[3]=((palette.colorAt(i)>>0)&0xFF)/255.f;
|
||||
paletteVecs.push_back(v);
|
||||
}
|
||||
|
||||
uint8_t codebook[2048]; memset(codebook,0,sizeof(codebook));
|
||||
Twiddler nibbleLUT(4,4);
|
||||
for(int i=0;i<vq.codeCount();i++){
|
||||
const Vec<64>& vec=vq.codeVector(i);
|
||||
for(int j=0;j<16;j++){
|
||||
Vec<4> col;
|
||||
int base=nibbleLUT.index(j)*4;
|
||||
for(int k=0;k<4;k++) col[k]=vec[base+k];
|
||||
|
||||
int closest=0;
|
||||
float best=Vec<4>::distanceSquared(paletteVecs[0],col);
|
||||
for(size_t pi=1;pi<paletteVecs.size();pi++){
|
||||
float d=Vec<4>::distanceSquared(paletteVecs[pi],col);
|
||||
if(d<best){best=d;closest=(int)pi;}
|
||||
}
|
||||
int byte=j/2, nib=j%2;
|
||||
if(nib==1) codebook[i*8+byte]|=(closest&0xF)<<4;
|
||||
else codebook[i*8+byte]|=(closest&0xF);
|
||||
}
|
||||
}
|
||||
|
||||
stream.write((char*)codebook,2048);
|
||||
|
||||
|
||||
for(const auto& vec:vectors){
|
||||
int codeIdx=vq.findClosest(vec);
|
||||
uint8_t c=(uint8_t)codeIdx;
|
||||
stream.write((char*)&c,1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void writeCompressed8BPPData(std::ostream& stream,const uf::stl::vector<Image>& indexedImages,const Palette& palette){
|
||||
VectorQuantizer<32> vq;
|
||||
uf::stl::vector<Vec<32>> vectors;
|
||||
|
||||
for(const auto& img:indexedImages){
|
||||
if(img.width()<MIN_MIPMAP_PALVQ||img.height()<MIN_MIPMAP_PALVQ) continue;
|
||||
int blocks=(img.width()*img.height())/16;
|
||||
Twiddler twiddler(img.width()/4,img.height()/4);
|
||||
for(int j=0;j<blocks;j++){
|
||||
int tw=twiddler.index(j);
|
||||
int x=(tw%(img.width()/4))*4; int y=(tw/(img.width()/4))*4;
|
||||
Vec<32> v1; v1.zero(); grab2x4Block(img,palette,x,y,v1,STORE_FULL); vectors.push_back(v1);
|
||||
Vec<32> v2; v2.zero(); grab2x4Block(img,palette,x+2,y,v2,STORE_FULL); vectors.push_back(v2);
|
||||
}
|
||||
}
|
||||
vq.compress(vectors,256);
|
||||
|
||||
|
||||
uf::stl::vector<Vec<4>> paletteVecs;
|
||||
for(int i=0;i<palette.colorCount();i++){
|
||||
Vec<4> v; v[0]=((palette.colorAt(i)>>24)&0xFF)/255.f;
|
||||
v[1]=((palette.colorAt(i)>>16)&0xFF)/255.f;
|
||||
v[2]=((palette.colorAt(i)>>8)&0xFF)/255.f;
|
||||
v[3]=((palette.colorAt(i)>>0)&0xFF)/255.f;
|
||||
paletteVecs.push_back(v);
|
||||
}
|
||||
|
||||
uint8_t codebook[2048]; memset(codebook,0,sizeof(codebook));
|
||||
Twiddler nibbleLUT(2,4);
|
||||
for(int i=0;i<vq.codeCount();i++){
|
||||
const Vec<32>& vec=vq.codeVector(i);
|
||||
for(int j=0;j<8;j++){
|
||||
Vec<4> col;
|
||||
int base=nibbleLUT.index(j)*4;
|
||||
for(int k=0;k<4;k++) col[k]=vec[base+k];
|
||||
int closest=0;
|
||||
float best=Vec<4>::distanceSquared(paletteVecs[0],col);
|
||||
for(size_t pi=1;pi<paletteVecs.size();pi++){
|
||||
float d=Vec<4>::distanceSquared(paletteVecs[pi],col);
|
||||
if(d<best){best=d;closest=(int)pi;}
|
||||
}
|
||||
codebook[i*8+j]=(uint8_t)closest;
|
||||
}
|
||||
}
|
||||
stream.write((char*)codebook,2048);
|
||||
|
||||
|
||||
if(indexedImages.size()>1) writeZeroes(stream,1);
|
||||
|
||||
|
||||
for(const auto& vec:vectors){
|
||||
uint8_t idx=(uint8_t)vq.findClosest(vec);
|
||||
stream.write((char*)&idx,1);
|
||||
}
|
||||
}
|
||||
#endif
|
118
dep/src/texconv/image.cpp
Normal file
118
dep/src/texconv/image.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <stb/stb_image.h>
|
||||
#include <stb/stb_image_write.h>
|
||||
|
||||
#include <texconv/image.h>
|
||||
|
||||
Image::Image() : w(0), h(0), indexedMode(false) {}
|
||||
Image::Image(int width, int height) : w(width), h(height), indexedMode(false) {
|
||||
pixels.resize(w*h);
|
||||
}
|
||||
|
||||
bool Image::loadFromFile(const uf::stl::string& path ) {
|
||||
int channels;
|
||||
uint8_t* buffer = stbi_load(path.c_str(), &w, &h, &channels, STBI_rgb_alpha);
|
||||
if (!buffer) {
|
||||
std::cerr<<"[ERROR] Failed to load image: "<<path<<"\n";
|
||||
return false;
|
||||
}
|
||||
indexedMode=false;
|
||||
pixels.resize(w*h);
|
||||
std::memcpy(pixels.data(), buffer, w*h*4);
|
||||
stbi_image_free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Image::saveToFile(const uf::stl::string& path) const {
|
||||
uf::stl::vector<uint8_t> buffer(w*h*4);
|
||||
for(int y=0;y<h;y++) for(int x=0;x<w;x++) {
|
||||
RGBA c=pixel(x,y);
|
||||
int idx=(y*w+x)*4;
|
||||
buffer[idx+0]=c.r;
|
||||
buffer[idx+1]=c.g;
|
||||
buffer[idx+2]=c.b;
|
||||
buffer[idx+3]=c.a;
|
||||
}
|
||||
return stbi_write_png(path.c_str(),w,h,4,buffer.data(),w*4)!=0;
|
||||
}
|
||||
|
||||
int Image::width() const { return w; }
|
||||
int Image::height() const { return h; }
|
||||
|
||||
RGBA Image::pixel(int x,int y) const {
|
||||
return pixels[y*w+x];
|
||||
}
|
||||
|
||||
void Image::setPixel(int x,int y, RGBA pixel) {
|
||||
if (!indexedMode) {
|
||||
pixels[y*w+x] = pixel;
|
||||
}
|
||||
}
|
||||
|
||||
Image Image::scaled(int newW,int newH,bool nearest) const {
|
||||
Image out(newW,newH);
|
||||
if (nearest) {
|
||||
for (int y=0;y<newH;y++) {
|
||||
for (int x=0;x<newW;x++) {
|
||||
int srcX = x * w / newW;
|
||||
int srcY = y * h / newH;
|
||||
out.pixels[y*newW+x] = pixel(srcX,srcY);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int y=0;y<newH;y++) {
|
||||
for (int x=0;x<newW;x++) {
|
||||
float gx = (float)x * (w-1) / (float)(newW-1);
|
||||
float gy = (float)y * (h-1) / (float)(newH-1);
|
||||
int x0 = (int)gx;
|
||||
int y0 = (int)gy;
|
||||
int x1 = std::min(x0+1,w-1);
|
||||
int y1 = std::min(y0+1,h-1);
|
||||
float dx = gx-x0;
|
||||
float dy = gy-y0;
|
||||
auto lerp=[&](uint8_t a,uint8_t b,float t){ return (uint8_t)(a*(1-t)+b*t); };
|
||||
RGBA c00=pixel(x0,y0);
|
||||
RGBA c10=pixel(x1,y0);
|
||||
RGBA c01=pixel(x0,y1);
|
||||
RGBA c11=pixel(x1,y1);
|
||||
RGBA top{
|
||||
lerp(c00.r,c10.r,dx),
|
||||
lerp(c00.g,c10.g,dx),
|
||||
lerp(c00.b,c10.b,dx),
|
||||
lerp(c00.a,c10.a,dx)};
|
||||
RGBA bottom{
|
||||
lerp(c01.r,c11.r,dx),
|
||||
lerp(c01.g,c11.g,dx),
|
||||
lerp(c01.b,c11.b,dx),
|
||||
lerp(c01.a,c11.a,dx)};
|
||||
out.pixels[y*newW+x]=RGBA{
|
||||
lerp(top.r,bottom.r,dy),
|
||||
lerp(top.g,bottom.g,dy),
|
||||
lerp(top.b,bottom.b,dy),
|
||||
lerp(top.a,bottom.a,dy)};
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Image::allocateIndexed(int colors) {
|
||||
indexedMode=true;
|
||||
indexed.assign(w*h,0);
|
||||
}
|
||||
|
||||
void Image::setIndexedPixel(int x,int y,uint8_t index) {
|
||||
if(indexedMode) {
|
||||
indexed[y*w+x]=index;
|
||||
}
|
||||
}
|
||||
uint8_t Image::indexedPixelAt(int x,int y) const {
|
||||
if(indexedMode) return indexed[y*w+x];
|
||||
return 0;
|
||||
}
|
||||
#endif
|
95
dep/src/texconv/imagecontainer.cpp
Normal file
95
dep/src/texconv/imagecontainer.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <texconv/imagecontainer.h>
|
||||
#include <texconv/common.h>
|
||||
|
||||
bool ImageContainer::load(const uf::stl::vector<uf::stl::string>& filenames, int textureType, int mipmapFilter) {
|
||||
bool mipmapped = (textureType & FLAG_MIPMAPPED);
|
||||
|
||||
if ((filenames.size() > 1) && !mipmapped) {
|
||||
std::cerr << "[ERROR] Only one input file may be specified if no mipmap flag is set.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
for (const auto& filename : filenames) {
|
||||
Image img;
|
||||
if (!img.loadFromFile(filename)) {
|
||||
std::cerr << "[ERROR] Failed to load image: " << filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidSize(img.width(), img.height(), textureType)) {
|
||||
std::cerr << "[ERROR] Image " << filename
|
||||
<< " has invalid texture size "
|
||||
<< img.width() << "x" << img.height() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mipmapped && img.width() != img.height()) {
|
||||
std::cerr << "[ERROR] Image " << filename
|
||||
<< " is not square. Mipmapped textures require square images.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
textureWidth = std::max(textureWidth, img.width());
|
||||
textureHeight = std::max(textureHeight, img.height());
|
||||
|
||||
images[img.width()] = img;
|
||||
std::cout << "[INFO] Loaded image " << filename << "\n";
|
||||
}
|
||||
|
||||
if (mipmapped) {
|
||||
if (mipmapFilter == 0) {
|
||||
std::cout << "[INFO] Using nearest-neighbor filtering for mipmaps\n";
|
||||
} else {
|
||||
std::cout << "[INFO] Using bilinear filtering for mipmaps\n";
|
||||
}
|
||||
|
||||
|
||||
for (int size = TEXTURE_SIZE_MAX/2; size >= 1; size /= 2) {
|
||||
if (images.count(size*2) && !images.count(size)) {
|
||||
Image mipmap = images[size*2].scaled(size, size,
|
||||
mipmapFilter == 0);
|
||||
images[size] = mipmap;
|
||||
std::cout << "[INFO] Generated " << size << "x" << size << " mipmap\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textureWidth < TEXTURE_SIZE_MIN || textureHeight < TEXTURE_SIZE_MIN) {
|
||||
std::cerr << "[ERROR] At least one input image must be 8x8 or larger.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
keys.clear();
|
||||
for (auto& kv : images) keys.push_back(kv.first);
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageContainer::unloadAll() {
|
||||
textureWidth = 0;
|
||||
textureHeight = 0;
|
||||
images.clear();
|
||||
keys.clear();
|
||||
}
|
||||
|
||||
const Image& ImageContainer::getByIndex(int index, bool ascending) const {
|
||||
if (index >= (int)keys.size()) {
|
||||
static Image dummy;
|
||||
return dummy;
|
||||
} else {
|
||||
int realIdx = ascending ? index : ((int)keys.size() - index - 1);
|
||||
int size = keys[realIdx];
|
||||
return images.at(size);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
81
dep/src/texconv/palette.cpp
Normal file
81
dep/src/texconv/palette.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <texconv/palette.h>
|
||||
#include <texconv/imagecontainer.h>
|
||||
|
||||
Palette::Palette(const ImageContainer& images) {
|
||||
for (int i=0;i<images.imageCount();i++) {
|
||||
const Image& img=images.getByIndex(i);
|
||||
for (int y=0;y<img.height();y++)
|
||||
for (int x=0;x<img.width();x++)
|
||||
insert(packColor(img.pixel(x,y)));
|
||||
}
|
||||
}
|
||||
|
||||
void Palette::insert(uint32_t color) {
|
||||
if (colorsMap.find(color)==colorsMap.end()) {
|
||||
int idx=(int)colorsVec.size();
|
||||
colorsMap[color]=idx;
|
||||
colorsVec.push_back(color);
|
||||
}
|
||||
}
|
||||
|
||||
int Palette::indexOf(uint32_t argb) const {
|
||||
auto it=colorsMap.find(argb);
|
||||
return (it!=colorsMap.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
uint32_t Palette::colorAt(int index) const {
|
||||
if (index>=0 && index<(int)colorsVec.size())
|
||||
return colorsVec[index];
|
||||
return 0xFF000000;
|
||||
}
|
||||
|
||||
bool Palette::save(const uf::stl::string& filename) const {
|
||||
std::ofstream out(filename,std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
std::cerr<<"[ERROR] Failed to open "<<filename<<" for writing\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
out.write(TEXTURE_MAGIC,4);
|
||||
int32_t n=colorCount();
|
||||
out.write((char*)&n,sizeof(int32_t));
|
||||
|
||||
|
||||
for (uint32_t c:colorsVec)
|
||||
out.write((char*)&c,sizeof(uint32_t));
|
||||
|
||||
out.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Palette::load(const uf::stl::string& filename) {
|
||||
std::ifstream in(filename,std::ios::binary);
|
||||
if (!in.is_open()) {
|
||||
std::cerr<<"[ERROR] Failed to open "<<filename<<" for reading\n";
|
||||
return false;
|
||||
}
|
||||
char magic[4];
|
||||
in.read(magic,4);
|
||||
if (memcmp(magic,PALETTE_MAGIC,4)!=0) {
|
||||
std::cerr<<"[ERROR] "<<filename<<" is not a valid palette file\n";
|
||||
return false;
|
||||
}
|
||||
int32_t numColors=0;
|
||||
in.read((char*)&numColors,sizeof(int32_t));
|
||||
|
||||
clear();
|
||||
for (int i=0;i<numColors;i++) {
|
||||
uint32_t c;
|
||||
in.read((char*)&c,sizeof(uint32_t));
|
||||
insert(c);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
280
dep/src/texconv/preview.cpp
Normal file
280
dep/src/texconv/preview.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <texconv/common.h>
|
||||
#include <texconv/twiddler.h>
|
||||
#include <texconv/palette.h>
|
||||
#include <texconv/image.h>
|
||||
#include <texconv/vqtools.h>
|
||||
|
||||
static const uint32_t colorCodes[256] = {
|
||||
0xffffffff, 0xe3aaaa, 0xffc7c7, 0xaac7c7, 0xaac7aa, 0xaaaae3, 0xaaaaff, 0xaae3ff,
|
||||
0xffffaae3, 0xe3ffaa, 0xffffaa, 0xffaaff, 0xaaffc7, 0xe3c7ff, 0xc7aaaa, 0xe3e3e3,
|
||||
0xffaa7171, 0xc78e8e, 0x718e8e, 0x718e71, 0x7171aa, 0x7171c7, 0x71aac7, 0xc771aa,
|
||||
0xffaac771, 0xc7c771, 0xc771c7, 0x71c78e, 0xaa8ec7, 0x8e7171, 0xaaaaaa, 0xc7c7c7,
|
||||
0xff710000, 0x8e1c1c, 0x381c1c, 0x381c00, 0x380038, 0x380055, 0x383855, 0x8e0038,
|
||||
0xff715500, 0x8e5500, 0x8e0055, 0x38551c, 0x711c55, 0x550000, 0x713838, 0x8e5555,
|
||||
0xffaa38aa, 0xc755c7, 0x7155c7, 0x7155aa, 0x7138e3, 0x7138ff, 0x7171ff, 0xc738e3,
|
||||
0xffaa8eaa, 0xc78eaa, 0xc738ff, 0x718ec7, 0xaa55ff, 0x8e38aa, 0xaa71e3, 0xc78eff,
|
||||
0xff38aa38, 0x55c755, 0x00c755, 0x00c738, 0x00aa71, 0x00aa8e, 0x00e38e, 0x55aa71,
|
||||
0xff38ff38, 0x55ff38, 0x55aa8e, 0x00ff55, 0x38c78e, 0x1caa38, 0x38e371, 0x55ff8e,
|
||||
0xffe300aa, 0xff1cc7, 0xaa1cc7, 0xaa1caa, 0xaa00e3, 0xaa00ff, 0xaa38ff, 0xff00e3,
|
||||
0xffe355aa, 0xff55aa, 0xff00ff, 0xaa55c7, 0xe31cff, 0xc700aa, 0xe338e3, 0xff55ff,
|
||||
0xffe3aa00, 0xffc71c, 0xaac71c, 0xaac700, 0xaaaa38, 0xaaaa55, 0xaae355, 0xffaa38,
|
||||
0xffe3ff00, 0xffff00, 0xffaa55, 0xaaff1c, 0xe3c755, 0xc7aa00, 0xe3e338, 0xffff55,
|
||||
0xffaaaa00, 0xc7c71c, 0x71c71c, 0x71c700, 0x71aa38, 0x71aa55, 0x71e355, 0xc7aa38,
|
||||
0xffaaff00, 0xc7ff00, 0xc7aa55, 0x71ff1c, 0xaac755, 0x8eaa00, 0xaae338, 0xc7ff55,
|
||||
0xffe30071, 0xff1c8e, 0xaa1c8e, 0xaa1c71, 0xaa00aa, 0xaa00c7, 0xaa38c7, 0xff00aa,
|
||||
0xffe35571, 0xff5571, 0xff00c7, 0xaa558e, 0xe31cc7, 0xc70071, 0xe338aa, 0xff55c7,
|
||||
0xff3871aa, 0x558ec7, 0x008ec7, 0x008eaa, 0x0071e3, 0x0071ff, 0x00aaff, 0x5571e3,
|
||||
0xff38c7aa, 0x55c7aa, 0x5571ff, 0x00c7c7, 0x388eff, 0x1c71aa, 0x38aae3, 0x55c7ff,
|
||||
0xff3800aa, 0x551cc7, 0x001cc7, 0x001caa, 0x0000e3, 0x0000ff, 0x0038ff, 0x5500e3,
|
||||
0xff3855aa, 0x5555aa, 0x5500ff, 0x0055c7, 0x381cff, 0x1c00aa, 0x3838e3, 0x5555ff,
|
||||
0xff380071, 0x551c8e, 0x001c8e, 0x001c71, 0x0000aa, 0x0000c7, 0x0038c7, 0x5500aa,
|
||||
0xff385571, 0x555571, 0x5500c7, 0x00558e, 0x381cc7, 0x1c0071, 0x3838aa, 0x5555c7,
|
||||
0xff383800, 0x55551c, 0x00551c, 0x005500, 0x003838, 0x003855, 0x007155, 0x553838,
|
||||
0xff388e00, 0x558e00, 0x553855, 0x008e1c, 0x385555, 0x1c3800, 0x387138, 0x558e55,
|
||||
0xff383838, 0x555555, 0x005555, 0x005538, 0x003871, 0x00388e, 0x00718e, 0x553871,
|
||||
0xff388e38, 0x558e38, 0x55388e, 0x008e55, 0x38558e, 0x1c3838, 0x387171, 0x558e8e,
|
||||
0xffe33838, 0xff5555, 0xaa5555, 0xaa5538, 0xaa3871, 0xaa388e, 0xaa718e, 0xff3871,
|
||||
0xffe38e38, 0xff8e38, 0xff388e, 0xaa8e55, 0xe3558e, 0xc73838, 0xe37171, 0xff8e8e,
|
||||
0xffaa0000, 0xc71c1c, 0x711c1c, 0x711c00, 0x710038, 0x710055, 0x713855, 0xc70038,
|
||||
0xffaa5500, 0xc75500, 0xc70055, 0x71551c, 0xaa1c55, 0x8e0000, 0xaa3838, 0xc75555
|
||||
};
|
||||
|
||||
static void drawBlock(Image& img,int x,int y,int w,int h,int codeIndex) {
|
||||
RGBA c;
|
||||
uint32_t packed = colorCodes[codeIndex%256];
|
||||
c.a=(packed>>24)&0xFF;
|
||||
c.r=(packed>>16)&0xFF;
|
||||
c.g=(packed>>8)&0xFF;
|
||||
c.b=(packed>>0)&0xFF;
|
||||
for(int yy=y;yy<y+h;yy++)
|
||||
for(int xx=x;xx<x+w;xx++)
|
||||
img.setPixel(xx,yy,c);
|
||||
}
|
||||
|
||||
static Image allocatePreview(int w,int h,bool mipmaps) {
|
||||
int ww=mipmaps? (w+w/2):w;
|
||||
Image img(ww,h);
|
||||
for(int y=0;y<h;y++) for(int x=0;x<ww;x++)
|
||||
img.setPixel(x,y,RGBA{0,0,0,0});
|
||||
return img;
|
||||
}
|
||||
|
||||
static void blitImage(Image& dst,const Image& src,int ox,int oy){
|
||||
for(int y=0;y<src.height();y++){
|
||||
for(int x=0;x<src.width();x++){
|
||||
dst.setPixel(ox+x,oy+y,src.pixel(x,y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool generatePreview(const uf::stl::string& texFile,
|
||||
const uf::stl::string& palFile,
|
||||
const uf::stl::string& previewFile,
|
||||
const uf::stl::string& codeUsageFile) {
|
||||
char magic[4];
|
||||
int16_t width,height;
|
||||
int32_t textureType,textureSize;
|
||||
bool genPreview=!previewFile.empty();
|
||||
bool genUsage=!codeUsageFile.empty();
|
||||
|
||||
std::ifstream in(texFile,std::ios::binary);
|
||||
if(!in.is_open()) {std::cerr<<"[ERROR] Cannot open "<<texFile<<"\n";return false;}
|
||||
in.read(magic,4);
|
||||
in.read((char*)&width,2);
|
||||
in.read((char*)&height,2);
|
||||
in.read((char*)&textureType,4);
|
||||
in.read((char*)&textureSize,4);
|
||||
if(std::memcmp(magic,TEXTURE_MAGIC,4)!=0) {
|
||||
std::cerr<<"Bad texture magic\n"; return false;
|
||||
}
|
||||
uf::stl::vector<uint8_t> data(textureSize);
|
||||
in.read((char*)data.data(),textureSize);
|
||||
in.close();
|
||||
|
||||
if(textureType&FLAG_STRIDED) width=(textureType&31)*32;
|
||||
int pixelFormat=(textureType>>PIXELFORMAT_SHIFT)&PIXELFORMAT_MASK;
|
||||
|
||||
uf::stl::vector<Image> decoded;
|
||||
uf::stl::vector<Image> usage;
|
||||
|
||||
if(textureType&FLAG_STRIDED) {
|
||||
Image img(width,height);
|
||||
if(pixelFormat==PIXELFORMAT_YUV422) {
|
||||
for(int y=0;y<height;y++){
|
||||
for(int x=0;x<width;x+=2){
|
||||
uint16_t p0,p1; std::memcpy(&p0,&data[(y*width+x+0)*2],2);
|
||||
std::memcpy(&p1,&data[(y*width+x+1)*2],2);
|
||||
RGBA c0,c1; YUV422toRGB(p0,p1,c0,c1);
|
||||
img.setPixel(x,y,c0);
|
||||
img.setPixel(x+1,y,c1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(int y=0;y<height;y++){
|
||||
for(int x=0;x<width;x++){
|
||||
uint16_t px; std::memcpy(&px,&data[(y*width+x)*2],2);
|
||||
img.setPixel(x,y,to32BPP(px,pixelFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
decoded.push_back(img);
|
||||
}
|
||||
else if(is16BPP(textureType) && !(textureType&FLAG_COMPRESSED)) {
|
||||
int curW=width,curH=height;
|
||||
int offset=0;
|
||||
if(textureType&FLAG_MIPMAPPED){curW=1;curH=1;offset=MIPMAP_OFFSET_16BPP;}
|
||||
while(curW<=width && curH<=height) {
|
||||
Image img(curW,curH);
|
||||
Twiddler tw(curW,curH); int pixels=curW*curH;
|
||||
for(int i=0;i<pixels;i++){
|
||||
uint16_t px; std::memcpy(&px,&data[offset+i*2],2);
|
||||
RGBA c=to32BPP(px,pixelFormat);
|
||||
int idx=tw.index(i);
|
||||
img.setPixel(idx%curW,idx/curW,c);
|
||||
}
|
||||
decoded.insert(decoded.begin(),img);
|
||||
offset+=curW*curH*2; curW*=2;curH*=2;
|
||||
}
|
||||
}
|
||||
else if(isPaletted(textureType) && !(textureType&FLAG_COMPRESSED)) {
|
||||
Palette pal; if(!pal.load(palFile)) return false;
|
||||
if(isFormat(textureType,PIXELFORMAT_PAL4BPP)){
|
||||
int curW=width,curH=height,offset=0;
|
||||
if(textureType&FLAG_MIPMAPPED){curW=1;curH=1;offset=MIPMAP_OFFSET_4BPP;}
|
||||
while(curW<=width&&curH<=height){
|
||||
Image img(curW,curH);
|
||||
Twiddler tw(curW,curH);
|
||||
if(curW==1&&curH==1){
|
||||
int idx=data[offset]&0xF;
|
||||
RGBA c=unpackColor(pal.colorAt(idx));
|
||||
img.setPixel(0,0,c); offset++;
|
||||
} else {
|
||||
int pixels=(curW*curH)/2;
|
||||
for(int i=0;i<pixels;i++){
|
||||
uint8_t byte=data[offset+i];
|
||||
int idx0=byte&0xF, idx1=(byte>>4)&0xF;
|
||||
int tw0=tw.index(i*2+0), tw1=tw.index(i*2+1);
|
||||
img.setPixel(tw0%curW,tw0/curW,unpackColor(pal.colorAt(idx0)));
|
||||
img.setPixel(tw1%curW,tw1/curW,unpackColor(pal.colorAt(idx1)));
|
||||
}
|
||||
offset+=(curW*curH)/2;
|
||||
}
|
||||
decoded.insert(decoded.begin(),img);
|
||||
curW*=2;curH*=2;
|
||||
}
|
||||
} else if(isFormat(textureType,PIXELFORMAT_PAL8BPP)){
|
||||
int curW=width,curH=height,offset=0;
|
||||
if(textureType&FLAG_MIPMAPPED){curW=1;curH=1;offset=MIPMAP_OFFSET_8BPP;}
|
||||
while(curW<=width&&curH<=height){
|
||||
Image img(curW,curH);
|
||||
Twiddler tw(curW,curH); int pixels=curW*curH;
|
||||
for(int i=0;i<pixels;i++){
|
||||
uint8_t idx=data[offset+i];
|
||||
int twi=tw.index(i);
|
||||
img.setPixel(twi%curW,twi/curW,unpackColor(pal.colorAt(idx)));
|
||||
}
|
||||
decoded.insert(decoded.begin(),img);
|
||||
offset+=curW*curH; curW*=2;curH*=2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(is16BPP(textureType) && (textureType&FLAG_COMPRESSED)) {
|
||||
int curW=width,curH=height,offset=2048;
|
||||
if(textureType&FLAG_MIPMAPPED){curW=2;curH=2;offset+=1;}
|
||||
while(curW<=width&&curH<=height){
|
||||
Image img(curW,curH), cui(curW,curH);
|
||||
if(genPreview) for(int y=0;y<curH;y++)for(int x=0;x<curW;x++) img.setPixel(x,y,{0,0,0,0});
|
||||
if(genUsage) for(int y=0;y<curH;y++)for(int x=0;x<curW;x++) cui.setPixel(x,y,{0,0,0,0});
|
||||
Twiddler tw(curW/2,curH/2); int pixels=(curW/2)*(curH/2);
|
||||
for(int i=0;i<pixels;i++){
|
||||
int cbidx=data[offset+i];
|
||||
uint16_t texel0,texel1,texel2,texel3;
|
||||
std::memcpy(&texel0,&data[cbidx*8+0],2);
|
||||
std::memcpy(&texel1,&data[cbidx*8+2],2);
|
||||
std::memcpy(&texel2,&data[cbidx*8+4],2);
|
||||
std::memcpy(&texel3,&data[cbidx*8+6],2);
|
||||
int twi=tw.index(i);
|
||||
int x=(twi%(curW/2))*2,y=(twi/(curW/2))*2;
|
||||
if(genPreview){
|
||||
RGBA p0=to32BPP(texel0,pixelFormat);
|
||||
RGBA p1=to32BPP(texel1,pixelFormat);
|
||||
RGBA p2=to32BPP(texel2,pixelFormat);
|
||||
RGBA p3=to32BPP(texel3,pixelFormat);
|
||||
img.setPixel(x+0,y+0,p0);
|
||||
img.setPixel(x+0,y+1,p1);
|
||||
img.setPixel(x+1,y+0,p2);
|
||||
img.setPixel(x+1,y+1,p3);
|
||||
}
|
||||
if(genUsage) drawBlock(cui,x,y,2,2,cbidx);
|
||||
}
|
||||
if(genPreview) decoded.insert(decoded.begin(),img);
|
||||
if(genUsage) usage.insert(usage.begin(),cui);
|
||||
offset+=(curW*curH)/4; curW*=2; curH*=2;
|
||||
}
|
||||
}
|
||||
else if(isPaletted(textureType) && (textureType&FLAG_COMPRESSED)) {
|
||||
Palette pal; if(!pal.load(palFile)) return false;
|
||||
int curW=width,curH=height,offset=2048;
|
||||
if(textureType&FLAG_MIPMAPPED){
|
||||
curW=(isFormat(textureType,PIXELFORMAT_PAL8BPP)?4:4);
|
||||
curH=4; offset+=1;
|
||||
}
|
||||
while(curW<=width&&curH<=height){
|
||||
Image img(curW,curH), cui(curW,curH);
|
||||
if(genPreview) for(int y=0;y<curH;y++)for(int x=0;x<curW;x++) img.setPixel(x,y,{0,0,0,0});
|
||||
if(genUsage) for(int y=0;y<curH;y++)for(int x=0;x<curW;x++) cui.setPixel(x,y,{0,0,0,0});
|
||||
Twiddler tw(curW/4,curH/4); int pixels=(curW/4)*(curH/4);
|
||||
for(int i=0;i<pixels;i++){
|
||||
int cb0=data[offset+i*2+0], cb1=data[offset+i*2+1];
|
||||
int twi=tw.index(i); int x=(twi%(curW/4))*4,y=(twi/(curW/4))*4;
|
||||
if(genPreview){
|
||||
for(int j=0;j<8;j++){
|
||||
int idx=data[cb0*8+j]; RGBA c=unpackColor(pal.colorAt(idx));
|
||||
img.setPixel(x+(j%2),y+(j/2),c);
|
||||
}
|
||||
for(int j=0;j<8;j++){
|
||||
int idx=data[cb1*8+j]; RGBA c=unpackColor(pal.colorAt(idx));
|
||||
img.setPixel(x+2+(j%2),y+(j/2),c);
|
||||
}
|
||||
}
|
||||
if(genUsage){
|
||||
drawBlock(cui,x,y,2,4,cb0);
|
||||
drawBlock(cui,x+2,y,2,4,cb1);
|
||||
}
|
||||
}
|
||||
if(genPreview) decoded.insert(decoded.begin(),img);
|
||||
if(genUsage) usage.insert(usage.begin(),cui);
|
||||
offset+=(curW*curH)/8; curW*=2;curH*=2;
|
||||
}
|
||||
}
|
||||
|
||||
if(genPreview && !decoded.empty()) {
|
||||
if(decoded.size()==1) decoded[0].saveToFile(previewFile);
|
||||
else {
|
||||
Image canvas=allocatePreview(width,height,true);
|
||||
int ox=0,oy=0;
|
||||
for(auto& im:decoded){ blitImage(canvas,im,ox,oy);
|
||||
if(ox==0){ox=im.width(); oy=0;} else {oy+=im.height();} }
|
||||
canvas.saveToFile(previewFile);
|
||||
}
|
||||
}
|
||||
if(genUsage && !usage.empty()) {
|
||||
if(usage.size()==1) usage[0].saveToFile(codeUsageFile);
|
||||
else {
|
||||
Image canvas=allocatePreview(width,height,true);
|
||||
int ox=0,oy=0;
|
||||
for(auto& im:usage){ blitImage(canvas,im,ox,oy);
|
||||
if(ox==0){ox=im.width();oy=0;} else {oy+=im.height();} }
|
||||
canvas.saveToFile(codeUsageFile);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
45
dep/src/texconv/twiddler.cpp
Normal file
45
dep/src/texconv/twiddler.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <texconv/twiddler.h>
|
||||
|
||||
Twiddler::Twiddler(int w, int h) {
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
m_index = new int[m_width * m_height];
|
||||
|
||||
int index = 0;
|
||||
|
||||
if (m_width < m_height)
|
||||
for (int y=0; y<m_height; y+=m_width)
|
||||
index += twiddle(m_index, m_width, 0, y, m_width, index);
|
||||
else
|
||||
for (int x=0; x<m_width; x+=m_height)
|
||||
index += twiddle(m_index, m_width, x, 0, m_height, index);
|
||||
}
|
||||
|
||||
Twiddler::~Twiddler() {
|
||||
delete[] m_index;
|
||||
}
|
||||
|
||||
int Twiddler::twiddle(int* output, int stride, int x, int y, int blocksize, int seq) const {
|
||||
int before = seq;
|
||||
|
||||
switch (blocksize) {
|
||||
case 1:
|
||||
output[seq++] = y * stride + x;
|
||||
break;
|
||||
default:
|
||||
blocksize = blocksize >> 1;
|
||||
seq += twiddle(output, stride, x, y, blocksize, seq);
|
||||
seq += twiddle(output, stride, x, y + blocksize, blocksize, seq);
|
||||
seq += twiddle(output, stride, x + blocksize, y, blocksize, seq);
|
||||
seq += twiddle(output, stride, x + blocksize, y + blocksize, blocksize, seq);
|
||||
break;
|
||||
}
|
||||
|
||||
return (seq - before);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
27
engine/inc/uf/ext/texconv/texconv.h
Normal file
27
engine/inc/uf/ext/texconv/texconv.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
|
||||
#include <uf/utils/memory/string.h>
|
||||
#include <uf/utils/memory/vector.h>
|
||||
|
||||
namespace ext {
|
||||
namespace texconv {
|
||||
struct TextureOptions {
|
||||
uf::stl::vector<uf::stl::string> inputs;
|
||||
uf::stl::string output;
|
||||
uf::stl::string format;
|
||||
bool mipmap = false;
|
||||
bool compress = false;
|
||||
bool stride = false;
|
||||
bool nearest = false;
|
||||
bool bilinear = false;
|
||||
bool verbose = false;
|
||||
|
||||
uf::stl::string previewFile;
|
||||
uf::stl::string codeUsageFile;
|
||||
};
|
||||
|
||||
bool UF_API convertTexture( const TextureOptions& opts );
|
||||
}
|
||||
}
|
@ -21,7 +21,20 @@ namespace uf {
|
||||
std::size_t m_format;
|
||||
public:
|
||||
// C-tor
|
||||
bool open( const uf::stl::string& filename, bool = true ); // from file
|
||||
Image();
|
||||
explicit Image(const vec2_t& size);
|
||||
Image(container_t&& move, const vec2_t& size);
|
||||
Image(const container_t& copy, const vec2_t& size);
|
||||
Image(const Image& copy);
|
||||
Image(Image&& move) noexcept;
|
||||
|
||||
~Image() = default;
|
||||
|
||||
// Assignment
|
||||
Image& operator=(const Image& copy);
|
||||
Image& operator=(Image&& move) noexcept;
|
||||
|
||||
bool open( const uf::stl::string& filename, bool = true ); // from file
|
||||
void open( const std::istream& stream ); // from stream
|
||||
void move( Image::container_t&& move, const Image::vec2_t& size ); // move from vector of pixels
|
||||
void copy( const Image::container_t& copy, const Image::vec2_t& size ); // copy from vector of pixels
|
||||
@ -54,16 +67,16 @@ namespace uf {
|
||||
size_t getFormat() const;
|
||||
|
||||
Image::pixel_t at( const Image::vec2_t& at );
|
||||
|
||||
// Modifiers
|
||||
void flip();
|
||||
void padToPowerOfTwo();
|
||||
bool save( const uf::stl::string& filename, bool flip = false ) const; // to file
|
||||
bool save( const uf::stl::string& filename, bool flip = false ) const; // to file
|
||||
void save( std::ostream& stream ) const; // to stream
|
||||
void convert( const uf::stl::string&, const uf::stl::string& = "rgba" );
|
||||
Image overlay(const Image& top, const Image::vec2_t& corner = {} ) const; // Merges one image on top of another
|
||||
Image replace(const Image::pixel_t& from, const Image::pixel_t& to ) const; // Changes all pixel from one color (from), to another (to)
|
||||
Image subImage( const Image::vec2_t& start, const Image::vec2_t& end) const; // Crops an image
|
||||
// Operators
|
||||
uf::Image& operator=(const uf::Image&);
|
||||
Image scale( const Image::vec2_t& size, bool nearest );
|
||||
};
|
||||
}
|
@ -35,7 +35,7 @@ namespace uf {
|
||||
// wrapper in case this gets called without feeding into a buffer directly to avoid additional memory allocations
|
||||
inline uf::stl::string readAsString( const uf::stl::string& filename, const uf::stl::string& hash = "" ) {
|
||||
uf::stl::string string;
|
||||
readAsString( filename, hash );
|
||||
readAsString( string, filename, hash );
|
||||
return string;
|
||||
}
|
||||
inline uf::stl::vector<uint8_t> readAsBuffer( const uf::stl::string& _filename, const uf::stl::string& hash = "" ) {
|
||||
|
@ -885,6 +885,8 @@ void UF_API uf::tick() {
|
||||
|
||||
auto& controller = uf::scene::getCurrentScene().getController();
|
||||
|
||||
// to-do: handle when the memory pool is disabled, because entities are NOT cleaned up at all
|
||||
// should also handle entity deletion when GC is disabled
|
||||
if ( ::config.engine.gc.enabled ) {
|
||||
TIMER( ::config.engine.gc.every ) {
|
||||
size_t collected = uf::instantiator::collect( ::config.engine.gc.mode );
|
||||
|
@ -229,7 +229,7 @@ namespace {
|
||||
|
||||
ext::json::reserve( json["buffers"], mesh.buffers.size() );
|
||||
for ( auto i = 0; i < mesh.buffers.size(); ++i ) {
|
||||
const uf::stl::string filename = settings.filename + ".buffer." + std::to_string(i) + "." + ( settings.compression != "" ? settings.compression : "bin" );
|
||||
const uf::stl::string filename = settings.filename + ".buffer." + std::to_string(i) + "." + ( settings.compression == "none" ? "bin" : settings.compression );
|
||||
uf::io::write( filename, mesh.buffers[i] );
|
||||
json["buffers"].emplace_back(uf::io::filename( filename ));
|
||||
}
|
||||
@ -270,6 +270,10 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
|
||||
/*.filename = */directory + "/graph.json",
|
||||
/*.conversion = */graph.metadata["exporter"]["conversion"].as<uf::stl::string>(),
|
||||
};
|
||||
|
||||
if ( graph.metadata["exporter"]["compression"].is<bool>() ) {
|
||||
settings.compression = graph.metadata["exporter"]["compression"].as<bool>() ? "auto" : "none";
|
||||
}
|
||||
|
||||
if ( settings.encoding == "auto" ) settings.encoding = ext::json::PREFERRED_ENCODING;
|
||||
if ( settings.compression == "auto" ) settings.compression = ext::json::PREFERRED_COMPRESSION;
|
||||
|
99
engine/src/ext/texconv/texconv.cpp
Normal file
99
engine/src/ext/texconv/texconv.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
// Modified from https://github.com/tvspelsfreak/texconv
|
||||
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_DC_TEXCONV
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <texconv/common.h>
|
||||
#include <texconv/imagecontainer.h>
|
||||
|
||||
#include <uf/ext/texconv/texconv.h>
|
||||
|
||||
bool ext::texconv::convertTexture( const ext::texconv::TextureOptions& opts ) {
|
||||
uf::stl::unordered_map<uf::stl::string,int> formats = {
|
||||
{"ARGB1555",PIXELFORMAT_ARGB1555},
|
||||
{"RGB565", PIXELFORMAT_RGB565},
|
||||
{"ARGB4444",PIXELFORMAT_ARGB4444},
|
||||
{"YUV422", PIXELFORMAT_YUV422},
|
||||
{"BUMPMAP", PIXELFORMAT_BUMPMAP},
|
||||
{"PAL4BPP", PIXELFORMAT_PAL4BPP},
|
||||
{"PAL8BPP", PIXELFORMAT_PAL8BPP}
|
||||
};
|
||||
|
||||
if ( opts.inputs.empty() ) {
|
||||
UF_MSG_ERROR( "No input file(s) specified" );
|
||||
return false;
|
||||
}
|
||||
if ( opts.output.empty() ) {
|
||||
UF_MSG_ERROR( "No output file specified" );
|
||||
return false;
|
||||
}
|
||||
|
||||
int pixFmt = -1;
|
||||
auto it = formats.find( opts.format );
|
||||
if ( it != formats.end() ) pixFmt = it->second;
|
||||
if ( pixFmt == -1 ){
|
||||
UF_MSG_ERROR( "Unsupported format: {}", opts.format );
|
||||
return false;
|
||||
}
|
||||
|
||||
uf::stl::string palFile = opts.output + ".pal";
|
||||
|
||||
int textureType = ( pixFmt<<PIXELFORMAT_SHIFT );
|
||||
if ( opts.mipmap ) textureType |= FLAG_MIPMAPPED;
|
||||
if ( opts.compress ) textureType |= FLAG_COMPRESSED;
|
||||
if ( opts.stride ) textureType |= ( FLAG_STRIDED|FLAG_NONTWIDDLED );
|
||||
|
||||
int filter = ( isPaletted( textureType ) || opts.nearest ) ? 0 : 1;
|
||||
if ( opts.bilinear ) filter = 1;
|
||||
|
||||
ImageContainer images;
|
||||
if ( !images.load( opts.inputs, textureType, filter ) ) return false;
|
||||
|
||||
if ( textureType&FLAG_STRIDED ){
|
||||
int stride = images.width()/32;
|
||||
textureType |= stride;
|
||||
}
|
||||
|
||||
std::ofstream out( opts.output, std::ios::binary );
|
||||
if ( !out.is_open() ){
|
||||
UF_MSG_ERROR( "Failed to open output file {}", opts.output );
|
||||
return false;
|
||||
}
|
||||
|
||||
int expectedSize = writeTextureHeader( out, images.width(), images.height(), textureType );
|
||||
auto before = out.tellp();
|
||||
|
||||
if ( isPaletted( textureType ) ){
|
||||
convertPaletted( out, images, textureType, palFile );
|
||||
} else {
|
||||
convert16BPP( out, images, textureType );
|
||||
}
|
||||
|
||||
auto after = out.tellp();
|
||||
int padding = expectedSize - ( (int) after - (int) before );
|
||||
if ( padding > 0 ){
|
||||
writeZeroes( out, padding );
|
||||
UF_MSG_INFO( "Added {} padding bytes", std::to_string( padding ) );
|
||||
}
|
||||
out.close();
|
||||
UF_MSG_INFO( "Wrote texture {}", opts.output );
|
||||
|
||||
// Preview & Code usage
|
||||
if ( !opts.previewFile.empty() || ( !opts.codeUsageFile.empty() && ( textureType&FLAG_COMPRESSED ) ) ){
|
||||
if ( generatePreview( opts.output,palFile,opts.previewFile,opts.codeUsageFile ) ){
|
||||
if ( !opts.previewFile.empty() ) UF_MSG_INFO( "Saved preview {}", opts.previewFile );
|
||||
if ( !opts.codeUsageFile.empty() ) UF_MSG_INFO( "Saved code usage {}", opts.codeUsageFile );
|
||||
} else {
|
||||
if ( !opts.previewFile.empty() ) UF_MSG_ERROR( "Failed to save preview {}", opts.previewFile );
|
||||
if ( !opts.codeUsageFile.empty() ) UF_MSG_ERROR( "Failed to save code usage {}", opts.codeUsageFile );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
@ -8,71 +8,60 @@
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <gltf/stb_image.h>
|
||||
#include <gltf/stb_image_write.h>
|
||||
#include <stb/stb_image.h>
|
||||
#include <stb/stb_image_write.h>
|
||||
#include <uf/utils/renderer/renderer.h>
|
||||
#include <uf/utils/string/ext.h>
|
||||
|
||||
// C-tor
|
||||
// Default
|
||||
/*
|
||||
uf::Image::Image() :
|
||||
m_bpp(8),
|
||||
m_channels(4),
|
||||
m_format(0) {
|
||||
uf::Image::Image() : m_bpp(8), m_channels(4), m_format(0) {
|
||||
m_dimensions = {0,0};
|
||||
}
|
||||
|
||||
uf::Image::Image(const vec2_t& size) : m_dimensions(size), m_bpp(8), m_channels(4), m_format(0) {
|
||||
m_pixels.resize(size.x * size.y * m_channels);
|
||||
}
|
||||
// Just Size
|
||||
uf::Image::Image( const Image::vec2_t& size ) :
|
||||
m_dimensions(size),
|
||||
m_bpp(8),
|
||||
m_channels(4),
|
||||
m_format(0)
|
||||
{
|
||||
this->m_pixels.reserve(size.x*size.y*this->m_channels);
|
||||
}
|
||||
// Move pixels
|
||||
uf::Image::Image( Image&& move ) :
|
||||
m_pixels(std::move(move.m_pixels)),
|
||||
m_dimensions(std::move(move.m_dimensions)),
|
||||
m_bpp(move.m_bpp),
|
||||
m_channels(move.m_channels),
|
||||
m_filename(move.m_filename),
|
||||
m_format(move.m_format)
|
||||
{
|
||||
|
||||
}
|
||||
// Copy pixels
|
||||
uf::Image::Image( const Image& copy ) :
|
||||
m_pixels(copy.m_pixels),
|
||||
m_dimensions(copy.m_dimensions),
|
||||
m_bpp(copy.m_bpp),
|
||||
m_channels(copy.m_channels),
|
||||
m_filename(copy.m_filename),
|
||||
m_format(copy.m_format)
|
||||
{
|
||||
uf::Image::Image(container_t&& move, const vec2_t& size) : m_pixels(std::move(move)), m_dimensions(size),
|
||||
m_bpp(8), m_channels(4), m_format(0) {}
|
||||
|
||||
}
|
||||
// Move from vector of pixels
|
||||
uf::Image::Image( Image::container_t&& move, const Image::vec2_t& size ) :
|
||||
m_pixels(std::move(move)),
|
||||
m_dimensions(size),
|
||||
m_bpp(8),
|
||||
m_channels(4),
|
||||
m_format(0)
|
||||
{
|
||||
uf::Image::Image(const container_t& copy, const vec2_t& size) : m_pixels(copy), m_dimensions(size),
|
||||
m_bpp(8), m_channels(4), m_format(0) {}
|
||||
|
||||
}
|
||||
// Copy from vector of pixels
|
||||
uf::Image::Image( const Image::container_t& copy, const Image::vec2_t& size ) :
|
||||
m_pixels(copy),
|
||||
m_dimensions(size),
|
||||
m_bpp(8),
|
||||
m_channels(4),
|
||||
m_format(0)
|
||||
{
|
||||
uf::Image::Image(const Image& copy) : m_pixels(copy.m_pixels),
|
||||
m_dimensions(copy.m_dimensions),
|
||||
m_bpp(copy.m_bpp), m_channels(copy.m_channels),
|
||||
m_filename(copy.m_filename), m_format(copy.m_format) {}
|
||||
|
||||
uf::Image::Image(Image&& move) noexcept : m_pixels(std::move(move.m_pixels)),
|
||||
m_dimensions(move.m_dimensions),
|
||||
m_bpp(move.m_bpp), m_channels(move.m_channels),
|
||||
m_filename(std::move(move.m_filename)),
|
||||
m_format(move.m_format) {}
|
||||
|
||||
uf::Image& uf::Image::operator=(const Image& copy) {
|
||||
if ( this != © ) {
|
||||
m_pixels = copy.m_pixels;
|
||||
m_dimensions = copy.m_dimensions;
|
||||
m_bpp = copy.m_bpp;
|
||||
m_channels = copy.m_channels;
|
||||
m_filename = copy.m_filename;
|
||||
m_format = copy.m_format;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
|
||||
uf::Image& uf::Image::operator=(Image&& move) noexcept {
|
||||
if ( this != &move ) {
|
||||
m_pixels = std::move(move.m_pixels);
|
||||
m_dimensions = move.m_dimensions;
|
||||
m_bpp = move.m_bpp;
|
||||
m_channels = move.m_channels;
|
||||
m_filename = std::move(move.m_filename);
|
||||
m_format = move.m_format;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
uf::stl::string uf::Image::getFilename() const {
|
||||
return this->m_filename;
|
||||
}
|
||||
@ -328,6 +317,7 @@ uf::Image::pixel_t uf::Image::at( const uf::Image::vec2_t& at ) {
|
||||
this->m_pixels[i++],
|
||||
};
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
// to file
|
||||
bool uf::Image::save( const uf::stl::string& filename, bool flip ) const {
|
||||
@ -349,7 +339,6 @@ bool uf::Image::save( const uf::stl::string& filename, bool flip ) const {
|
||||
void uf::Image::save( std::ostream& stream ) const {
|
||||
|
||||
}
|
||||
#include <uf/utils/string/ext.h>
|
||||
void uf::Image::convert( const uf::stl::string& from, const uf::stl::string& to ) {
|
||||
uf::Image::container_t pixels = std::move(this->m_pixels);
|
||||
if ( uf::string::lowercase(to) != "rgba" ) {
|
||||
@ -383,60 +372,92 @@ void uf::Image::convert( const uf::stl::string& from, const uf::stl::string& to
|
||||
}
|
||||
// Merges one image on top of another
|
||||
uf::Image uf::Image::overlay(const Image& top, const Image::vec2_t& corner) const {
|
||||
return *this;
|
||||
Image out(*this);
|
||||
for (size_t y = 0; y < top.m_dimensions.y; ++y) {
|
||||
for (size_t x = 0; x < top.m_dimensions.x; ++x) {
|
||||
size_t dstX = corner.x + x;
|
||||
size_t dstY = corner.y + y;
|
||||
if (dstX >= m_dimensions.x || dstY >= m_dimensions.y) continue;
|
||||
size_t dstIdx = (dstY*m_dimensions.x + dstX) * m_channels;
|
||||
size_t srcIdx = (y*top.m_dimensions.x + x) * top.m_channels;
|
||||
|
||||
float alpha = top.m_pixels[srcIdx+3] / 255.0f;
|
||||
for (size_t c = 0; c < 3; ++c) {
|
||||
out.m_pixels[dstIdx+c] =
|
||||
static_cast<uint8_t>( (1-alpha)*out.m_pixels[dstIdx+c] +
|
||||
alpha*top.m_pixels[srcIdx+c] );
|
||||
}
|
||||
out.m_pixels[dstIdx+3] = 255;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
// Changes all pixel from one color (from), to another (to)
|
||||
uf::Image uf::Image::replace(const Image::pixel_t& from, const Image::pixel_t& to ) const {
|
||||
return *this;
|
||||
Image out(*this);
|
||||
for (size_t i = 0; i < out.m_pixels.size(); i+=out.m_channels) {
|
||||
if (out.m_pixels[i] == from[0] &&
|
||||
out.m_pixels[i+1] == from[1] &&
|
||||
out.m_pixels[i+2] == from[2] &&
|
||||
out.m_pixels[i+3] == from[3]) {
|
||||
out.m_pixels[i] = to[0];
|
||||
out.m_pixels[i+1] = to[1];
|
||||
out.m_pixels[i+2] = to[2];
|
||||
out.m_pixels[i+3] = to[3];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
// Crops an image
|
||||
uf::Image uf::Image::subImage( const Image::vec2_t& start, const Image::vec2_t& end) const {
|
||||
return *this;
|
||||
/*
|
||||
pod::Vector2ui size = parameter;
|
||||
if ( mode == uf::Image::CropMode::START_SPAN_SIZE ) size = parameter;
|
||||
if ( mode == uf::Image::CropMode::START_TO_END ) size = parameter - start;
|
||||
if ( size > this->size ) return uf::Image(*this);
|
||||
|
||||
|
||||
uint len = size.product() * this->bpp / 8;
|
||||
uint8_t* src = (uint8_t*) this->raw;
|
||||
uint8_t* dst = new uint8_t[len];
|
||||
|
||||
uint minX = start.x();
|
||||
uint minY = start.y();
|
||||
uint dstWidth = size.x();
|
||||
uint dstHeight = size.x();
|
||||
uint srcWidth = this->size.x();
|
||||
uint srcHeight = this->size.y();
|
||||
|
||||
for(uint x = minX; x < dstWidth + minX; x++) {
|
||||
for(uint y = minY; y < dstHeight + minY; y++) {
|
||||
uint srcOffset = y * srcWidth + x;
|
||||
uint dstOffset = (y - minY) * dstWidth + (x - minX);
|
||||
srcOffset *= this->bpp / 8;
|
||||
dstOffset *= this->bpp / 8;
|
||||
|
||||
for ( uint i = 0; i < this->bpp/4; i++ ) dst[dstOffset+i] = src[srcOffset+i];
|
||||
vec2_t size = { end.x - start.x, end.y - start.y };
|
||||
container_t outPixels(size.x * size.y * m_channels);
|
||||
for (size_t y = 0; y < size.y; ++y) {
|
||||
for (size_t x = 0; x < size.x; ++x) {
|
||||
size_t dstIdx = (y*size.x + x) * m_channels;
|
||||
size_t srcIdx = ((start.y+y)*m_dimensions.x + (start.x+x)) * m_channels;
|
||||
for (size_t c = 0; c < m_channels; ++c)
|
||||
outPixels[dstIdx+c] = m_pixels[srcIdx+c];
|
||||
}
|
||||
}
|
||||
|
||||
uf::Image image;
|
||||
image.raw = (uint8_t*) dst;
|
||||
image.len = len;
|
||||
image.bpp = this->bpp;
|
||||
image.size = size;
|
||||
image.format = this->format;
|
||||
return image;
|
||||
*/
|
||||
return Image(std::move(outPixels), size);
|
||||
}
|
||||
// Scales an image, nearest = true does nearest neighbor, nearest = false does bilinear interpolation
|
||||
uf::Image uf::Image::scale( const uf::Image::vec2_t& newSize, bool nearest ) {
|
||||
container_t outPixels(newSize.x * newSize.y * m_channels);
|
||||
float xRatio = static_cast<float>(m_dimensions.x) / newSize.x;
|
||||
float yRatio = static_cast<float>(m_dimensions.y) / newSize.y;
|
||||
|
||||
uf::Image& uf::Image::operator=( const uf::Image& copy ) {
|
||||
this->m_pixels = copy.m_pixels;
|
||||
this->m_dimensions = copy.m_dimensions;
|
||||
this->m_bpp = copy.m_bpp;
|
||||
this->m_channels = copy.m_channels;
|
||||
this->m_filename = copy.m_filename;
|
||||
this->m_format = copy.m_format;
|
||||
return *this;
|
||||
for (size_t j = 0; j < newSize.y; ++j) {
|
||||
for (size_t i = 0; i < newSize.x; ++i) {
|
||||
if (nearest) {
|
||||
size_t srcX = static_cast<size_t>(i * xRatio);
|
||||
size_t srcY = static_cast<size_t>(j * yRatio);
|
||||
size_t srcIdx = (srcY*m_dimensions.x + srcX) * m_channels;
|
||||
size_t dstIdx = (j*newSize.x + i) * m_channels;
|
||||
for (size_t c = 0; c < m_channels; ++c)
|
||||
outPixels[dstIdx+c] = m_pixels[srcIdx+c];
|
||||
} else {
|
||||
float gx = i * xRatio;
|
||||
float gy = j * yRatio;
|
||||
size_t x0 = static_cast<size_t>(gx);
|
||||
size_t y0 = static_cast<size_t>(gy);
|
||||
size_t x1 = std::min(x0+1, (size_t)m_dimensions.x-1);
|
||||
size_t y1 = std::min(y0+1, (size_t)m_dimensions.y-1);
|
||||
float u = gx - x0;
|
||||
float v = gy - y0;
|
||||
size_t dstIdx = (j*newSize.x + i) * m_channels;
|
||||
|
||||
for (size_t c=0; c<m_channels; ++c) {
|
||||
auto p00 = m_pixels[(y0*m_dimensions.x + x0)*m_channels + c];
|
||||
auto p10 = m_pixels[(y0*m_dimensions.x + x1)*m_channels + c];
|
||||
auto p01 = m_pixels[(y1*m_dimensions.x + x0)*m_channels + c];
|
||||
auto p11 = m_pixels[(y1*m_dimensions.x + x1)*m_channels + c];
|
||||
float val = (1-u)*(1-v)*p00 + u*(1-v)*p10 + (1-u)*v*p01 + u*v*p11;
|
||||
outPixels[dstIdx+c] = static_cast<uint8_t>(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Image(std::move(outPixels), newSize);
|
||||
}
|
Loading…
Reference in New Issue
Block a user