File: TextureLoaderPng.cpp

package info (click to toggle)
jazz2-native 3.5.0-1
  • links: PTS, VCS
  • area: contrib
  • in suites:
  • size: 16,836 kB
  • sloc: cpp: 172,557; xml: 113; python: 36; makefile: 5; sh: 2
file content (208 lines) | stat: -rw-r--r-- 5,931 bytes parent folder | download | duplicates (2)
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include "TextureLoaderPng.h"

#include <Base/Memory.h>
#include <Containers/SmallVector.h>
#include <IO/MemoryStream.h>
#include <IO/Compression/DeflateStream.h>

using namespace Death::Containers;
using namespace Death::IO;
using namespace Death::IO::Compression;
using namespace Death::Memory;

namespace nCine
{
	TextureLoaderPng::TextureLoaderPng(std::unique_ptr<Stream> fileHandle)
		: ITextureLoader(std::move(fileHandle))
	{
		constexpr std::uint8_t PngSignature[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
		constexpr std::uint8_t PngTypeIndexed = 1;
		constexpr std::uint8_t PngTypeColor = 2;

		if (!fileHandle_->IsValid()) {
			return;
		}

		// Check header signature
		std::uint8_t internalBuffer[sizeof(PngSignature)];
		fileHandle_->Read(internalBuffer, sizeof(PngSignature));
		if (std::memcmp(internalBuffer, PngSignature, sizeof(PngSignature)) != 0) {
			LOGE("Invalid PNG signature");
			return;
		}

		// Load image
		bool headerParsed = false;
		bool isPaletted = false;
		bool is24Bit = false;

		SmallVector<std::uint8_t, 0> data;

		while (true) {
			std::int32_t length = ReadInt32BigEndian(fileHandle_);
			std::uint32_t type = ReadInt32BigEndian(fileHandle_);

			if (!headerParsed && type != 'IHDR') {
				// Header does not appear first
				LOGE("Invalid IHDR signature");
				return;
			}

			std::int32_t blockEndPosition = std::int32_t(fileHandle_->GetPosition()) + length;

			switch (type) {
				case 'IHDR': {
					DEATH_ASSERT(!headerParsed, "PNG file is corrupted", );

					width_ = ReadInt32BigEndian(fileHandle_);
					height_ = ReadInt32BigEndian(fileHandle_);

					std::uint8_t bitDepth;
					fileHandle_->Read(&bitDepth, sizeof(bitDepth));
					std::uint8_t colorType;
					fileHandle_->Read(&colorType, sizeof(colorType));

					if (bitDepth == 8 && colorType == (PngTypeIndexed | PngTypeColor)) {
						isPaletted = true;
					}
					is24Bit = (colorType == PngTypeColor);

					std::int32_t dataLength = width_ * height_;
					if (!isPaletted) {
						dataLength *= 4;
					}

					pixels_ = std::make_unique<GLubyte[]>(dataLength);

					std::uint8_t compression;
					fileHandle_->Read(&compression, sizeof(compression));
					std::uint8_t filter;
					fileHandle_->Read(&filter, sizeof(filter));
					std::uint8_t interlace;
					fileHandle_->Read(&interlace, sizeof(interlace));

					if (compression != 0) {
						// Compression method is not supported
						LOGE("PNG file is not supported");
						return;
					}
					if (interlace != 0) {
						// Interlacing is not supported
						LOGE("PNG file is not supported");
						return;
					}

					headerParsed = true;
					break;
				}

				case 'IDAT': {
					std::int32_t prevlength = std::int32_t(data.size());
					std::int32_t newLength = prevlength + length;
					data.resize_for_overwrite(newLength);
					fileHandle_->Read(data.data() + prevlength, length);
					break;
				}

				case 'IEND': {
					std::size_t dataLength = 16 + (width_ * height_ * 5);
					auto buffer = std::make_unique<GLubyte[]>(dataLength);

					MemoryStream ms(data.data() + 2, data.size() - 2);
					DeflateStream uc(ms, std::int32_t(dataLength));
					uc.Read(buffer.get(), dataLength);
					DEATH_ASSERT(uc.IsValid(), "PNG file cannot be decompressed", );

					std::int32_t o = 0;
					std::int32_t pxStride = (isPaletted ? 1 : (is24Bit ? 3 : 4));
					std::int32_t srcStride = width_ * pxStride;
					std::int32_t dstStride = width_ * (isPaletted ? 1 : 4);

					auto bufferPrev = std::make_unique<GLubyte[]>(srcStride);

					for (std::int32_t y = 0; y < height_; y++) {
						// Read filter
						std::uint8_t filter = buffer[o++];

						// Read data
						GLubyte* bufferRow = &buffer[o];
						o += srcStride;

						for (std::int32_t i = 0; i < srcStride; i++) {
							if (i < pxStride) {
								bufferRow[i] = UnapplyFilter(filter, bufferRow[i], 0, bufferPrev[i], 0);
							} else {
								bufferRow[i] = UnapplyFilter(filter, bufferRow[i], bufferRow[i - pxStride], bufferPrev[i], bufferPrev[i - pxStride]);
							}
						}

						if (is24Bit) {
							for (std::int32_t i = 0; i < srcStride; i++) {
								std::memcpy(&pixels_[y * dstStride + 4 * i], &bufferRow[3 * i], 3);
								pixels_[y * dstStride + 4 * i + 3] = 255;
							}
						} else {
							std::memcpy(&pixels_[y * dstStride], bufferRow, srcStride);
						}

						std::memcpy(bufferPrev.get(), bufferRow, srcStride);
					}

					mipMapCount_ = 1;
					texFormat_ = TextureFormat(GL_RGBA8);
					hasLoaded_ = true;
					return;
				}

				default: {
					fileHandle_->Seek(length, SeekOrigin::Current);
					break;
				}
			}

			if (fileHandle_->GetPosition() != blockEndPosition) {
				// Block has incorrect length
				LOGE("PNG file is corrupted");
				return;
			}

			// Skip CRC
			fileHandle_->Seek(4, SeekOrigin::Current);
		}

		LOGE("PNG file is corrupted");
	}

	std::int32_t TextureLoaderPng::ReadInt32BigEndian(const std::unique_ptr<Stream>& s)
	{
		std::uint32_t value;
		s->Read(&value, sizeof(value));
		return AsBE(value);
	}

	std::uint8_t TextureLoaderPng::UnapplyFilter(std::uint8_t filter, std::uint8_t x, std::uint8_t a, std::uint8_t b, std::uint8_t c)
	{
		constexpr std::uint8_t PngFilterNone = 0;
		constexpr std::uint8_t PngFilterSub = 1;
		constexpr std::uint8_t PngFilterUp = 2;
		constexpr std::uint8_t PngFilterAverage = 3;
		constexpr std::uint8_t PngFilterPaeth = 4;

		switch (filter) {
			case PngFilterNone: return x;
			case PngFilterSub: return std::uint8_t(x + a);
			case PngFilterUp: return std::uint8_t(x + b);
			case PngFilterAverage: return std::uint8_t(x + (a + b) / 2);
			case PngFilterPaeth: {
				std::int32_t p = a + b - c;
				std::int32_t pa = std::abs(p - a);
				std::int32_t pb = std::abs(p - b);
				std::int32_t pc = std::abs(p - c);
				return (std::uint8_t)(x + ((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c));
			}

			// Unsupported filter specified
			default: return 0;
		}
	}
}