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
209
210
211
212
213
214
215
216
217
|
/**
@file GImage_ppm.cpp
@author Morgan McGuire, http://graphics.cs.williams.edu
@created 2002-05-27
@edited 2006-05-10
*/
#include "G3D/platform.h"
#include "G3D/GImage.h"
#include "G3D/BinaryInput.h"
#include "G3D/BinaryOutput.h"
#include "G3D/TextInput.h"
#include "G3D/TextOutput.h"
#include "G3D/Log.h"
namespace G3D {
void GImage::encodePPMASCII(
BinaryOutput& out) const {
TextOutput::Settings ppmOptions;
ppmOptions.convertNewlines = false;
ppmOptions.numColumns = 70;
ppmOptions.wordWrap = TextOutput::Settings::WRAP_WITHOUT_BREAKING;
TextOutput ppm(ppmOptions);
switch (m_channels) {
case 1:
{
ppm.printf("P2\n%d %d\n255\n", m_width, m_height);
const Color1uint8* c = this->pixel1();
// Insert newlines every 70 characters max
for (uint32 i = 0; i < (uint32)(m_width * m_height); ++i) {
ppm.printf("%d%c", c[i].value, (i % (70/4) == 0) ? '\n' : ' ');
}
}
break;
case 3:
{
ppm.printf("P3\n%d %d\n255\n", m_width, m_height);
const Color3uint8* c = this->pixel3();
// Insert newlines every 70 characters max
for (uint32 i = 0; i < (uint32)(m_width * m_height); ++i) {
ppm.printf("%d %d %d%c", c[i].r, c[i].g, c[i].b,
(i % (70/12) == 0) ?
'\n' : ' ');
}
}
break;
default:
alwaysAssertM(false, "PPM requires either 1 or 3 channels exactly.");
}
const std::string& s = ppm.commitString();
out.writeBytes(s.c_str(), s.length());
}
void GImage::encodePPM(
BinaryOutput& out) const {
// http://netpbm.sourceforge.net/doc/ppm.html
if (m_channels == 3) {
std::string header = format("P6 %d %d 255 ", m_width, m_height);
out.writeBytes(header.c_str(), header.size());
out.writeBytes(this->pixel3(), m_width * m_height * 3);
} else if (m_channels == 1) {
std::string header = format("P5 %d %d 255 ", m_width, m_height);
out.writeBytes(header.c_str(), header.size());
out.writeBytes(this->pixel1(), m_width * m_height);
} else {
alwaysAssertM(false, "PPM requires either 1 or 3 channels exactly.");
}
}
void GImage::decodePPMASCII(
BinaryInput& input) {
int ppmWidth;
int ppmHeight;
double maxColor;
// Create a TextInput object to parse ascii format
// Mixed binary/ascii formats will require more
const std::string inputStr = input.readString();
TextInput::Settings ppmOptions;
ppmOptions.cppLineComments = false;
ppmOptions.otherCommentCharacter = '#';
ppmOptions.signedNumbers = true;
ppmOptions.singleQuotedStrings = false;
TextInput ppmInput(TextInput::FROM_STRING, inputStr, ppmOptions);
//Skip first line in header P#
std::string ppmType = ppmInput.readSymbol();
ppmWidth = (int)ppmInput.readNumber();
ppmHeight = (int)ppmInput.readNumber();
// Everything but a PBM will have a max color value
if (ppmType != "P2") {
maxColor = ppmInput.readNumber();
} else {
maxColor = 255;
}
if ((ppmWidth < 0) ||
(ppmHeight < 0) ||
(maxColor <= 0)) {
throw GImage::Error("Invalid PPM Header.", input.getFilename());
}
// I don't think it's proper to scale values less than 255
if (maxColor <= 255.0) {
maxColor = 255.0;
}
m_width = ppmWidth;
m_height = ppmHeight;
m_channels = 3;
// always scale down to 1 byte per channel
m_byte = (uint8*)m_memMan->alloc(m_width * m_height * 3);
// Read in the image data. I am not validating if the values match the maxColor
// requirements. I only scale if needed to fit within the byte available.
for (uint32 i = 0; i < (uint32)(m_width * m_height); ++i) {
// read in color and scale to max pixel defined in header
// A max color less than 255 might need to be left alone and not scaled.
Color3uint8& curPixel = *(pixel3() + i);
if (ppmType == "P3") {
curPixel.r = (uint8)(ppmInput.readNumber() * (255.0 / maxColor));
curPixel.g = (uint8)(ppmInput.readNumber() * (255.0 / maxColor));
curPixel.b = (uint8)(ppmInput.readNumber() * (255.0 / maxColor));
} else if (ppmType == "P2") {
uint8 pixel = (uint8)(ppmInput.readNumber() * (255.0 / maxColor));
curPixel.r = pixel;
curPixel.g = pixel;
curPixel.b = pixel;
} else if (ppmType == "P1") {
int pixel = (uint8)(ppmInput.readNumber() * maxColor);
curPixel.r = pixel;
curPixel.g = pixel;
curPixel.b = pixel;
}
}
}
/** Consumes whitespace up to and including a number, but not the following character */
static int scanUInt(BinaryInput& input) {
char c = input.readUInt8();
while (isWhiteSpace(c)) {
c = input.readUInt8();
}
std::string s;
s += c;
c = input.readUInt8();
while (!isWhiteSpace(c)) {
s += c;
c = input.readUInt8();
}
// Back up one to avoid consuming the last character
input.setPosition(input.getPosition() - 1);
int x;
sscanf(s.c_str(), "%d", &x);
return x;
}
void GImage::decodePPM(
BinaryInput& input) {
char head[2];
int w, h;
input.readBytes(head, 2);
if (head[0] != 'P' || (head[1] != '6') && (head[1] != '5')) {
throw GImage::Error("Invalid PPM Header.", input.getFilename());
}
w = scanUInt(input);
h = scanUInt(input);
// Skip the max color specifier
scanUInt(input);
if ((w < 0) ||
(h < 0) ||
(w > 100000) ||
(h > 100000)) {
throw GImage::Error("Invalid PPM size in header.", input.getFilename());
}
// Trailing whitespace
input.readUInt8();
if (head[1] == '6') {
// 3 channel
resize(w, h, 3);
input.readBytes(m_byte, m_width * m_height * 3);
} else if (head[1] == '5') {
// 1 channel
resize(w, h, 1);
input.readBytes(m_byte, m_width * m_height);
}
}
}
|