WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
SoftwareImage.cpp
Go to the documentation of this file.
1#include "SoftwareImage.h"
2
3#include <algorithm>
4#include <cmath>
5#include <cstring>
6
7#include "stb_image.h"
8#include "stb_image_write.h"
9
10#include "Logger.h"
11
12SoftwareImage::SoftwareImage(int width, int height)
13 : w_(width), h_(height), pixels_(width * height * 4, 0)
14{
15}
16
17SoftwareImage::SoftwareImage(const uint8_t* bgraData, int width, int height)
18 : w_(width), h_(height), pixels_(bgraData, bgraData + width * height * 4)
19{
20}
21
22SoftwareImage SoftwareImage::scaled(int newWidth, int newHeight) const
23{
24 if (empty() || newWidth <= 0 || newHeight <= 0)
25 return {};
26
27 if (newWidth == w_ && newHeight == h_)
28 return *this;
29
30 SoftwareImage result(newWidth, newHeight);
31
32 const float xRatio = static_cast<float>(w_) / newWidth;
33 const float yRatio = static_cast<float>(h_) / newHeight;
34
35 for (int y = 0; y < newHeight; ++y)
36 {
37 const float srcY = y * yRatio;
38 const int y0 = static_cast<int>(srcY);
39 const int y1 = std::min(y0 + 1, h_ - 1);
40 const float fy = srcY - y0;
41
42 for (int x = 0; x < newWidth; ++x)
43 {
44 const float srcX = x * xRatio;
45 const int x0 = static_cast<int>(srcX);
46 const int x1 = std::min(x0 + 1, w_ - 1);
47 const float fx = srcX - x0;
48
49 const uint8_t* p00 = &pixels_[(y0 * w_ + x0) * 4];
50 const uint8_t* p10 = &pixels_[(y0 * w_ + x1) * 4];
51 const uint8_t* p01 = &pixels_[(y1 * w_ + x0) * 4];
52 const uint8_t* p11 = &pixels_[(y1 * w_ + x1) * 4];
53
54 uint8_t* dst = &result.pixels_[(y * newWidth + x) * 4];
55 for (int c = 0; c < 4; ++c)
56 {
57 const float top = p00[c] * (1.0f - fx) + p10[c] * fx;
58 const float bot = p01[c] * (1.0f - fx) + p11[c] * fx;
59 dst[c] = static_cast<uint8_t>(std::clamp(top * (1.0f - fy) + bot * fy, 0.0f, 255.0f));
60 }
61 }
62 }
63
64 return result;
65}
66
68{
69 if (empty())
70 return {};
71
72 SoftwareImage result(w_, h_);
73 const int rowBytes = w_ * 4;
74 for (int y = 0; y < h_; ++y)
75 std::memcpy(&result.pixels_[y * rowBytes], &pixels_[(h_ - 1 - y) * rowBytes], rowBytes);
76
77 return result;
78}
79
80// Convert BGRA buffer to RGBA in-place (for stb_image_write which expects RGBA)
81static void bgraToRgba(const uint8_t* src, uint8_t* dst, int count)
82{
83 for (int i = 0; i < count; ++i)
84 {
85 dst[i * 4 + 0] = src[i * 4 + 2]; // R
86 dst[i * 4 + 1] = src[i * 4 + 1]; // G
87 dst[i * 4 + 2] = src[i * 4 + 0]; // B
88 dst[i * 4 + 3] = src[i * 4 + 3]; // A
89 }
90}
91
92bool SoftwareImage::savePNG(const std::string& path) const
93{
94 if (empty())
95 return false;
96
97 const int count = w_ * h_;
98 std::vector<uint8_t> rgba(count * 4);
99 bgraToRgba(pixels_.data(), rgba.data(), count);
100
101 return stbi_write_png(path.c_str(), w_, h_, 4, rgba.data(), w_ * 4) != 0;
102}
103
104bool SoftwareImage::savePNG(const std::wstring& path) const
105{
106 // Convert wstring to UTF-8 string for stb_image_write
107 std::string utf8;
108 utf8.reserve(path.size());
109 for (wchar_t ch : path)
110 {
111 if (ch < 0x80)
112 utf8.push_back(static_cast<char>(ch));
113 else if (ch < 0x800)
114 {
115 utf8.push_back(static_cast<char>(0xC0 | (ch >> 6)));
116 utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
117 }
118 else
119 {
120 utf8.push_back(static_cast<char>(0xE0 | (ch >> 12)));
121 utf8.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
122 utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
123 }
124 }
125 return savePNG(utf8);
126}
127
128// Blend helpers
129static inline uint8_t clampByte(float v)
130{
131 return static_cast<uint8_t>(std::clamp(v, 0.0f, 255.0f));
132}
133
134static inline float overlayChannel(float base, float blend)
135{
136 // Overlay: if base < 0.5: 2*base*blend, else 1 - 2*(1-base)*(1-blend)
137 if (base < 0.5f)
138 return 2.0f * base * blend;
139 else
140 return 1.0f - 2.0f * (1.0f - base) * (1.0f - blend);
141}
142
143void SoftwareImage::composite(const SoftwareImage& src, int destX, int destY, int blendMode)
144{
145 if (src.empty() || empty())
146 return;
147
148 // Clip the source region to fit within destination
149 const int srcW = std::min(src.w_, w_ - destX);
150 const int srcH = std::min(src.h_, h_ - destY);
151
152 if (srcW <= 0 || srcH <= 0)
153 return;
154
155 for (int y = 0; y < srcH; ++y)
156 {
157 for (int x = 0; x < srcW; ++x)
158 {
159 const uint8_t* s = &src.pixels_[(y * src.w_ + x) * 4];
160 uint8_t* d = &pixels_[((destY + y) * w_ + (destX + x)) * 4];
161
162 const float sa = s[3] / 255.0f; // source alpha (BGRA: index 3 = A)
163
164 if (blendMode == 4) // Multiply
165 {
166 // Multiply blend: result = src * dst, then alpha-composite
167 for (int c = 0; c < 3; ++c)
168 {
169 const float sc = s[c] / 255.0f;
170 const float dc = d[c] / 255.0f;
171 const float blended = sc * dc;
172 d[c] = clampByte((blended * sa + dc * (1.0f - sa)) * 255.0f);
173 }
174 d[3] = clampByte((sa + (d[3] / 255.0f) * (1.0f - sa)) * 255.0f);
175 }
176 else if (blendMode == 6) // Overlay
177 {
178 for (int c = 0; c < 3; ++c)
179 {
180 const float sc = s[c] / 255.0f;
181 const float dc = d[c] / 255.0f;
182 const float blended = overlayChannel(dc, sc);
183 d[c] = clampByte((blended * sa + dc * (1.0f - sa)) * 255.0f);
184 }
185 d[3] = clampByte((sa + (d[3] / 255.0f) * (1.0f - sa)) * 255.0f);
186 }
187 else // SourceOver (default, blendMode == 1 or anything else)
188 {
189 const float da = d[3] / 255.0f;
190 const float outA = sa + da * (1.0f - sa);
191 if (outA > 0.0f)
192 {
193 for (int c = 0; c < 3; ++c)
194 {
195 const float sc = s[c] / 255.0f;
196 const float dc = d[c] / 255.0f;
197 d[c] = clampByte(((sc * sa + dc * da * (1.0f - sa)) / outA) * 255.0f);
198 }
199 }
200 d[3] = clampByte(outA * 255.0f);
201 }
202 }
203 }
204}
205
207{
208 w_ = src.w_;
209 h_ = src.h_;
210 pixels_ = src.pixels_;
211}
212
213SoftwareImage SoftwareImage::loadFromMemory(const uint8_t* data, int size)
214{
215 int w, h, channels;
216 uint8_t* rgb = stbi_load_from_memory(data, size, &w, &h, &channels, 4); // force RGBA
217
218 if (!rgb)
219 {
220 LOG_ERROR << "SoftwareImage::loadFromMemory failed:" << stbi_failure_reason();
221 return {};
222 }
223
224 // stb gives us RGBA, convert to BGRA for our internal format
225 SoftwareImage img(w, h);
226 const int count = w * h;
227 for (int i = 0; i < count; ++i)
228 {
229 img.pixels_[i * 4 + 0] = rgb[i * 4 + 2]; // B
230 img.pixels_[i * 4 + 1] = rgb[i * 4 + 1]; // G
231 img.pixels_[i * 4 + 2] = rgb[i * 4 + 0]; // R
232 img.pixels_[i * 4 + 3] = rgb[i * 4 + 3]; // A
233 }
234
235 stbi_image_free(rgb);
236 return img;
237}
#define LOG_ERROR
Definition Logger.h:11
static void bgraToRgba(const uint8_t *src, uint8_t *dst, int count)
static float overlayChannel(float base, float blend)
static uint8_t clampByte(float v)
CPU-side image buffer storing BGRA pixel data.
std::vector< uint8_t > pixels_
void composite(const SoftwareImage &src, int destX, int destY, int blendMode=1)
Composite a source image onto this image at the given position.
SoftwareImage mirrored() const
Return a vertically mirrored copy.
bool savePNG(const std::string &path) const
Save as PNG to the given file path.
SoftwareImage()=default
bool empty() const
True if the image has zero dimensions.
void assign(const SoftwareImage &src)
Replace contents entirely with a copy of src.
static SoftwareImage loadFromMemory(const uint8_t *data, int size)
Load a JPEG image from a memory buffer.
SoftwareImage scaled(int newWidth, int newHeight) const
Return a scaled copy using bilinear interpolation.
uint8_t * data()
Mutable pointer to the raw BGRA pixel buffer.