1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
// Encoding a PNG into an N64 texture is the one part of building soh.o2r that nothing else
// provides. Torch decodes N64 texture data out of a rom and libultraship decodes it again for
// rendering, but neither goes the other way. Everything else the packer needs -- the resource
// header, the texture type enum, size arithmetic, directory walking, zipping, portVersion --
// comes from torch, so this file is the whole of what had to be written.
//
// The quantisation follows ZAPD's ZTexture exactly, since these bytes have to match the archives
// ZAPD produced. n64graphics is not a substitute: it scales (x * 15 / 255) where ZAPD shifts
// (x >> 4), which differ for most inputs.
#include "PngTexture.h"
#include <cstdio>
#include <fstream>
#include <vector>
#include "binarytools/BinaryWriter.h"
#include "factories/BaseFactory.h"
#include "factories/ResourceType.h"
#include "n64graphics/stb_image.h"
#include "utils/TextureUtils.h"
namespace fs = std::filesystem;
namespace {
TextureType TypeFromString(const std::string& format) {
if (format == "rgba32") return TextureType::RGBA32bpp;
if (format == "rgb5a1") return TextureType::RGBA16bpp;
if (format == "ci4") return TextureType::Palette4bpp;
if (format == "ci8") return TextureType::Palette8bpp;
if (format == "i4") return TextureType::Grayscale4bpp;
if (format == "i8") return TextureType::Grayscale8bpp;
if (format == "ia4") return TextureType::GrayscaleAlpha4bpp;
if (format == "ia8") return TextureType::GrayscaleAlpha8bpp;
if (format == "ia16") return TextureType::GrayscaleAlpha16bpp;
return TextureType::Error;
}
std::vector<uint8_t> Encode(const uint8_t* rgba, int w, int h, TextureType type) {
auto px = [&](int y, int x, int c) -> uint8_t { return rgba[(((size_t)y * w) + x) * 4 + c]; };
std::vector<uint8_t> out(TextureUtils::CalculateTextureSize(type, w, h));
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
const size_t i = (size_t)y * w + x;
switch (type) {
case TextureType::RGBA32bpp:
out[i * 4 + 0] = px(y, x, 0);
out[i * 4 + 1] = px(y, x, 1);
out[i * 4 + 2] = px(y, x, 2);
out[i * 4 + 3] = px(y, x, 3);
break;
case TextureType::RGBA16bpp: {
const uint16_t data = ((px(y, x, 0) >> 3) << 11) | ((px(y, x, 1) >> 3) << 6) |
((px(y, x, 2) >> 3) << 1) | (px(y, x, 3) != 0);
out[i * 2 + 0] = (data & 0xFF00) >> 8;
out[i * 2 + 1] = (data & 0x00FF);
break;
}
case TextureType::Grayscale4bpp:
if (x % 2 == 0) {
out[i / 2] = (uint8_t)(((px(y, x, 0) / 16) << 4) + (px(y, x + 1, 0) / 16));
}
break;
case TextureType::Grayscale8bpp:
out[i] = px(y, x, 0);
break;
case TextureType::GrayscaleAlpha4bpp:
if (x % 2 == 0) {
const uint8_t hi = ((px(y, x, 0) >> 5) << 1) | (px(y, x, 3) != 0);
const uint8_t lo = ((px(y, x + 1, 0) >> 5) << 1) | (px(y, x + 1, 3) != 0);
out[i / 2] = (uint8_t)((hi << 4) | lo);
}
break;
case TextureType::GrayscaleAlpha8bpp:
out[i] = (uint8_t)((((px(y, x, 0) >> 4) & 0xF) << 4) | ((px(y, x, 3) >> 4) & 0xF));
break;
case TextureType::GrayscaleAlpha16bpp:
out[i * 2 + 0] = px(y, x, 0);
out[i * 2 + 1] = px(y, x, 3);
break;
// Palettes need a TLUT this packer has no way to build.
default:
return {};
}
}
}
return out;
}
} // namespace
namespace PngTexture {
bool IsFormat(const std::string& format) {
return TypeFromString(format) != TextureType::Error;
}
bool Convert(const fs::path& png, const fs::path& dest, const std::string& format) {
const TextureType type = TypeFromString(format);
int w = 0, h = 0, channels = 0;
uint8_t* pixels = stbi_load(png.string().c_str(), &w, &h, &channels, 4);
if (pixels == nullptr) {
fprintf(stderr, "%s: %s\n", png.string().c_str(), stbi_failure_reason());
return false;
}
const std::vector<uint8_t> encoded = Encode(pixels, w, h, type);
stbi_image_free(pixels);
if (encoded.empty()) {
fprintf(stderr, "%s: cannot encode %s\n", png.string().c_str(), format.c_str());
return false;
}
LUS::BinaryWriter writer;
BaseExporter::WriteHeader(writer, Torch::ResourceType::Texture, 0);
writer.Write((uint32_t)type);
writer.Write((uint32_t)w);
writer.Write((uint32_t)h);
writer.Write((uint32_t)encoded.size());
writer.Write((char*)encoded.data(), encoded.size());
const std::vector<char> payload = writer.ToVector();
writer.Close();
fs::create_directories(dest.parent_path());
std::ofstream out(dest, std::ios::binary);
out.write(payload.data(), payload.size());
return out.good();
}
} // namespace PngTexture
|