Skip to content

Commit 72cc868

Browse files
committed
Fix image pattern tiling scale by applying DPI correction from PNG and JPEG metadata.
1 parent 25996cc commit 72cc868

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

src/pagx/ppt/PPTExporter.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,8 +829,13 @@ void PPTWriter::writeColorSource(XMLBuilder& out, const ColorSource* source, flo
829829
pattern->tileModeY == TileMode::Repeat || pattern->tileModeY == TileMode::Mirror);
830830
if (needsTiling && hasDimensions && !shapeBounds.isEmpty()) {
831831
const auto& M = pattern->matrix;
832-
auto sx = static_cast<int>(std::round(static_cast<double>(M.a) * 100000.0));
833-
auto sy = static_cast<int>(std::round(static_cast<double>(M.d) * 100000.0));
832+
float imgDpiX = 72.0f;
833+
float imgDpiY = 72.0f;
834+
GetImageDPI(pattern->image, &imgDpiX, &imgDpiY);
835+
double dpiCorrX = static_cast<double>(imgDpiX) / 96.0;
836+
double dpiCorrY = static_cast<double>(imgDpiY) / 96.0;
837+
auto sx = static_cast<int>(std::round(M.a * dpiCorrX * 100000.0));
838+
auto sy = static_cast<int>(std::round(M.d * dpiCorrY * 100000.0));
834839
auto tx = PxToEMU(M.tx - shapeBounds.x);
835840
auto ty = PxToEMU(M.ty - shapeBounds.y);
836841
bool flipX = (pattern->tileModeX == TileMode::Mirror);

src/pagx/utils/ExporterUtils.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,85 @@ bool GetImageDimensions(const Image* image, int* width, int* height) {
267267
return false;
268268
}
269269

270+
static bool GetPNGDPI(const uint8_t* data, size_t size, float* dpiX, float* dpiY) {
271+
static const uint8_t kPNGSignature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
272+
if (size < 8 || memcmp(data, kPNGSignature, 8) != 0) {
273+
return false;
274+
}
275+
size_t offset = 8;
276+
while (offset + 12 <= size) {
277+
auto chunkLen = static_cast<uint32_t>((data[offset] << 24) | (data[offset + 1] << 16) |
278+
(data[offset + 2] << 8) | data[offset + 3]);
279+
if (memcmp(data + offset + 4, "pHYs", 4) == 0) {
280+
if (chunkLen == 9 && offset + 8 + 9 <= size) {
281+
const uint8_t* d = data + offset + 8;
282+
auto ppuX = static_cast<uint32_t>((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]);
283+
auto ppuY = static_cast<uint32_t>((d[4] << 24) | (d[5] << 16) | (d[6] << 8) | d[7]);
284+
uint8_t unit = d[8];
285+
if (unit == 1 && ppuX > 0 && ppuY > 0) {
286+
*dpiX = static_cast<float>(ppuX) * 0.0254f;
287+
*dpiY = static_cast<float>(ppuY) * 0.0254f;
288+
return true;
289+
}
290+
}
291+
return false;
292+
}
293+
if (memcmp(data + offset + 4, "IDAT", 4) == 0) {
294+
break;
295+
}
296+
offset += 12 + chunkLen;
297+
}
298+
return false;
299+
}
300+
301+
static bool GetJPEGDPI(const uint8_t* data, size_t size, float* dpiX, float* dpiY) {
302+
if (size < 2 || data[0] != 0xFF || data[1] != 0xD8) {
303+
return false;
304+
}
305+
size_t offset = 2;
306+
while (offset + 4 < size) {
307+
if (data[offset] != 0xFF) {
308+
return false;
309+
}
310+
uint8_t marker = data[offset + 1];
311+
if (marker == 0xD9 || marker == 0xDA) {
312+
break;
313+
}
314+
auto segLen = static_cast<uint16_t>((data[offset + 2] << 8) | data[offset + 3]);
315+
if (marker == 0xE0 && segLen >= 16 && offset + 2 + segLen <= size) {
316+
if (memcmp(data + offset + 4, "JFIF\0", 5) == 0) {
317+
uint8_t units = data[offset + 11];
318+
auto xDensity = static_cast<uint16_t>((data[offset + 12] << 8) | data[offset + 13]);
319+
auto yDensity = static_cast<uint16_t>((data[offset + 14] << 8) | data[offset + 15]);
320+
if (xDensity > 0 && yDensity > 0) {
321+
if (units == 1) {
322+
*dpiX = static_cast<float>(xDensity);
323+
*dpiY = static_cast<float>(yDensity);
324+
return true;
325+
} else if (units == 2) {
326+
*dpiX = static_cast<float>(xDensity) * 2.54f;
327+
*dpiY = static_cast<float>(yDensity) * 2.54f;
328+
return true;
329+
}
330+
}
331+
}
332+
}
333+
offset += 2 + segLen;
334+
}
335+
return false;
336+
}
337+
338+
bool GetImageDPI(const Image* image, float* dpiX, float* dpiY) {
339+
auto data = GetImageData(image);
340+
if (!data || data->size() == 0) {
341+
return false;
342+
}
343+
if (GetPNGDPI(data->bytes(), data->size(), dpiX, dpiY)) {
344+
return true;
345+
}
346+
return GetJPEGDPI(data->bytes(), data->size(), dpiX, dpiY);
347+
}
348+
270349
bool IsJPEG(const uint8_t* data, size_t size) {
271350
return size >= 2 && data[0] == 0xFF && data[1] == 0xD8;
272351
}

src/pagx/utils/ExporterUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ bool GetJPEGDimensions(const uint8_t* data, size_t size, int* width, int* height
7575

7676
bool GetImageDimensions(const Image* image, int* width, int* height);
7777

78+
bool GetImageDPI(const Image* image, float* dpiX, float* dpiY);
79+
7880
bool IsJPEG(const uint8_t* data, size_t size);
7981

8082
std::shared_ptr<tgfx::Data> GetImageData(const Image* image);

0 commit comments

Comments
 (0)