зеркало из https://github.com/mozilla/gecko-dev.git
290 строки
8.0 KiB
C++
290 строки
8.0 KiB
C++
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
bool error_msg(const char* message) {
|
|
fprintf(stderr, "%s\n", message);
|
|
return false;
|
|
}
|
|
#define return_on_error(X) \
|
|
if (!X) return false;
|
|
|
|
size_t Log2(uint32_t value) { return 31 - __builtin_clz(value); }
|
|
|
|
struct HeaderPNM {
|
|
size_t xsize;
|
|
size_t ysize;
|
|
bool is_gray; // PGM
|
|
bool has_alpha; // PAM
|
|
size_t bits_per_sample;
|
|
};
|
|
|
|
class Parser {
|
|
public:
|
|
explicit Parser(uint8_t* data, size_t length)
|
|
: pos_(data), end_(data + length) {}
|
|
|
|
// Sets "pos" to the first non-header byte/pixel on success.
|
|
bool ParseHeader(HeaderPNM* header, const uint8_t** pos) {
|
|
// codec.cc ensures we have at least two bytes => no range check here.
|
|
if (pos_[0] != 'P') return false;
|
|
const uint8_t type = pos_[1];
|
|
pos_ += 2;
|
|
|
|
switch (type) {
|
|
case '5':
|
|
header->is_gray = true;
|
|
return ParseHeaderPNM(header, pos);
|
|
|
|
case '6':
|
|
header->is_gray = false;
|
|
return ParseHeaderPNM(header, pos);
|
|
|
|
case '7':
|
|
return ParseHeaderPAM(header, pos);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Exposed for testing
|
|
bool ParseUnsigned(size_t* number) {
|
|
if (pos_ == end_) return error_msg("PNM: reached end before number");
|
|
if (!IsDigit(*pos_)) return error_msg("PNM: expected unsigned number");
|
|
|
|
*number = 0;
|
|
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
|
*number *= 10;
|
|
*number += *pos_ - '0';
|
|
++pos_;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ParseSigned(double* number) {
|
|
if (pos_ == end_) return error_msg("PNM: reached end before signed");
|
|
|
|
if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) {
|
|
return error_msg("PNM: expected signed number");
|
|
}
|
|
|
|
// Skip sign
|
|
const bool is_neg = *pos_ == '-';
|
|
if (is_neg || *pos_ == '+') {
|
|
++pos_;
|
|
if (pos_ == end_) return error_msg("PNM: reached end before digits");
|
|
}
|
|
|
|
// Leading digits
|
|
*number = 0.0;
|
|
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
|
*number *= 10;
|
|
*number += *pos_ - '0';
|
|
++pos_;
|
|
}
|
|
|
|
// Decimal places?
|
|
if (pos_ < end_ && *pos_ == '.') {
|
|
++pos_;
|
|
double place = 0.1;
|
|
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
|
*number += (*pos_ - '0') * place;
|
|
place *= 0.1;
|
|
++pos_;
|
|
}
|
|
}
|
|
|
|
if (is_neg) *number = -*number;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
|
|
static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
|
|
static bool IsWhitespace(const uint8_t c) {
|
|
return IsLineBreak(c) || c == '\t' || c == ' ';
|
|
}
|
|
|
|
bool SkipBlank() {
|
|
if (pos_ == end_) return error_msg("PNM: reached end before blank");
|
|
const uint8_t c = *pos_;
|
|
if (c != ' ' && c != '\n') return error_msg("PNM: expected blank");
|
|
++pos_;
|
|
return true;
|
|
}
|
|
|
|
bool SkipSingleWhitespace() {
|
|
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
|
if (!IsWhitespace(*pos_)) return error_msg("PNM: expected whitespace");
|
|
++pos_;
|
|
return true;
|
|
}
|
|
|
|
bool SkipWhitespace() {
|
|
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
|
if (!IsWhitespace(*pos_) && *pos_ != '#') {
|
|
return error_msg("PNM: expected whitespace/comment");
|
|
}
|
|
|
|
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
|
++pos_;
|
|
}
|
|
|
|
// Comment(s)
|
|
while (pos_ != end_ && *pos_ == '#') {
|
|
while (pos_ != end_ && !IsLineBreak(*pos_)) {
|
|
++pos_;
|
|
}
|
|
// Newline(s)
|
|
while (pos_ != end_ && IsLineBreak(*pos_)) pos_++;
|
|
}
|
|
|
|
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
|
++pos_;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MatchString(const char* keyword) {
|
|
const uint8_t* ppos = pos_;
|
|
while (*keyword) {
|
|
if (ppos >= end_) return error_msg("PAM: unexpected end of input");
|
|
if (*keyword != *ppos) return false;
|
|
ppos++;
|
|
keyword++;
|
|
}
|
|
pos_ = ppos;
|
|
return_on_error(SkipWhitespace());
|
|
return true;
|
|
}
|
|
|
|
bool ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
|
|
size_t num_channels = 3;
|
|
size_t max_val = 255;
|
|
while (!MatchString("ENDHDR")) {
|
|
return_on_error(SkipWhitespace());
|
|
if (MatchString("WIDTH")) {
|
|
return_on_error(ParseUnsigned(&header->xsize));
|
|
} else if (MatchString("HEIGHT")) {
|
|
return_on_error(ParseUnsigned(&header->ysize));
|
|
} else if (MatchString("DEPTH")) {
|
|
return_on_error(ParseUnsigned(&num_channels));
|
|
} else if (MatchString("MAXVAL")) {
|
|
return_on_error(ParseUnsigned(&max_val));
|
|
} else if (MatchString("TUPLTYPE")) {
|
|
if (MatchString("RGB_ALPHA")) {
|
|
header->has_alpha = true;
|
|
} else if (MatchString("RGB")) {
|
|
} else if (MatchString("GRAYSCALE_ALPHA")) {
|
|
header->has_alpha = true;
|
|
header->is_gray = true;
|
|
} else if (MatchString("GRAYSCALE")) {
|
|
header->is_gray = true;
|
|
} else if (MatchString("BLACKANDWHITE_ALPHA")) {
|
|
header->has_alpha = true;
|
|
header->is_gray = true;
|
|
max_val = 1;
|
|
} else if (MatchString("BLACKANDWHITE")) {
|
|
header->is_gray = true;
|
|
max_val = 1;
|
|
} else {
|
|
return error_msg("PAM: unknown TUPLTYPE");
|
|
}
|
|
} else {
|
|
return error_msg("PAM: unknown header keyword");
|
|
}
|
|
}
|
|
if (num_channels !=
|
|
(header->has_alpha ? 1 : 0) + (header->is_gray ? 1 : 3)) {
|
|
return error_msg("PAM: bad DEPTH");
|
|
}
|
|
if (max_val == 0 || max_val >= 65536) {
|
|
return error_msg("PAM: bad MAXVAL");
|
|
}
|
|
header->bits_per_sample = Log2(max_val + 1);
|
|
|
|
*pos = pos_;
|
|
return true;
|
|
}
|
|
|
|
bool ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) {
|
|
return_on_error(SkipWhitespace());
|
|
return_on_error(ParseUnsigned(&header->xsize));
|
|
|
|
return_on_error(SkipWhitespace());
|
|
return_on_error(ParseUnsigned(&header->ysize));
|
|
|
|
return_on_error(SkipWhitespace());
|
|
size_t max_val;
|
|
return_on_error(ParseUnsigned(&max_val));
|
|
if (max_val == 0 || max_val >= 65536) {
|
|
return error_msg("PNM: bad MaxVal");
|
|
}
|
|
header->bits_per_sample = Log2(max_val + 1);
|
|
|
|
return_on_error(SkipSingleWhitespace());
|
|
|
|
*pos = pos_;
|
|
return true;
|
|
}
|
|
|
|
const uint8_t* pos_;
|
|
const uint8_t* const end_;
|
|
};
|
|
|
|
bool load_file(unsigned char** out, size_t* outsize, const char* filename) {
|
|
FILE* file;
|
|
file = fopen(filename, "rb");
|
|
if (!file) return false;
|
|
if (fseek(file, 0, SEEK_END) != 0) {
|
|
fclose(file);
|
|
return false;
|
|
}
|
|
*outsize = ftell(file);
|
|
if (*outsize == LONG_MAX || *outsize < 9 || fseek(file, 0, SEEK_SET)) {
|
|
fclose(file);
|
|
return false;
|
|
}
|
|
*out = (unsigned char*)malloc(*outsize);
|
|
if (!(*out)) return false;
|
|
size_t readsize;
|
|
readsize = fread(*out, 1, *outsize, file);
|
|
fclose(file);
|
|
if (readsize != *outsize) return false;
|
|
return true;
|
|
}
|
|
|
|
bool DecodePAM(const char* filename, uint8_t** buffer, size_t* w, size_t* h,
|
|
size_t* nb_chans, size_t* bitdepth) {
|
|
unsigned char* in_file;
|
|
size_t in_size;
|
|
if (!load_file(&in_file, &in_size, filename))
|
|
return error_msg("Could not read input file");
|
|
Parser parser(in_file, in_size);
|
|
HeaderPNM header = {};
|
|
const uint8_t* pos = nullptr;
|
|
if (!parser.ParseHeader(&header, &pos)) return false;
|
|
|
|
if (header.bits_per_sample == 0 || header.bits_per_sample > 12) {
|
|
return error_msg("PNM: bits_per_sample invalid (can do at most 12-bit)");
|
|
}
|
|
*w = header.xsize;
|
|
*h = header.ysize;
|
|
*bitdepth = header.bits_per_sample;
|
|
*nb_chans = (header.is_gray ? 1 : 3) + (header.has_alpha ? 1 : 0);
|
|
|
|
size_t pnm_remaining_size = in_file + in_size - pos;
|
|
size_t buffer_size = *w * *h * *nb_chans * (*bitdepth > 8 ? 2 : 1);
|
|
if (pnm_remaining_size < buffer_size) {
|
|
return error_msg("PNM file too small");
|
|
}
|
|
*buffer = (uint8_t*)malloc(buffer_size);
|
|
memcpy(*buffer, pos, buffer_size);
|
|
return true;
|
|
}
|