gecko-dev/third_party/jpeg-xl/experimental/fast_lossless/pam-input.h

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;
}