Camconst support for multiple crops (#6473)

Adapted from ART 
Co-authored-by: Alberto Griggio <agriggio@users.noreply.github.com>

* camconst: support for multiple image sizes in raw_crop and masked_areas
* Clean up code after porting raw crop changes
* fixed raw crop for Canon R6 reduced-resolution raws
* Add Canon EOS R5 1.6 crop raw crop & masked areas
This commit is contained in:
Lawrence37 2023-01-01 01:50:11 -08:00 committed by GitHub
parent 9fef79ede4
commit d74524f2c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 213 additions and 76 deletions

View File

@ -28,8 +28,6 @@ namespace rtengine
CameraConst::CameraConst() : pdafOffset(0)
{
memset(dcraw_matrix, 0, sizeof(dcraw_matrix));
memset(raw_crop, 0, sizeof(raw_crop));
memset(raw_mask, 0, sizeof(raw_mask));
white_max = 0;
globalGreenEquilibration = -1;
}
@ -192,6 +190,68 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model)
std::unique_ptr<CameraConst> cc(new CameraConst);
cc->make_model = make_model;
const auto get_raw_crop =
[](int w, int h, const cJSON *ji, CameraConst *cc) -> bool
{
std::array<int, 4> rc;
if (ji->type != cJSON_Array) {
//fprintf(stderr, "\"raw_crop\" must be an array\n");
return false;
}
int i;
for (i = 0, ji = ji->child; i < 4 && ji != nullptr; i++, ji = ji->next) {
if (ji->type != cJSON_Number) {
//fprintf(stderr, "\"raw_crop\" array must contain numbers\n");
return false;
}
//cc->raw_crop[i] = ji->valueint;
rc[i] = ji->valueint;
}
if (i != 4 || ji != nullptr) {
//fprintf(stderr, "\"raw_crop\" must contain 4 numbers\n");
return false;
}
cc->raw_crop[std::make_pair(w, h)] = rc;
return true;
};
const auto get_masked_areas =
[](int w, int h, const cJSON *ji, CameraConst *cc) -> bool
{
std::array<std::array<int, 4>, 2> rm;
if (ji->type != cJSON_Array) {
//fprintf(stderr, "\"masked_areas\" must be an array\n");
return false;
}
int i;
for (i = 0, ji = ji->child; i < 2 * 4 && ji != nullptr; i++, ji = ji->next) {
if (ji->type != cJSON_Number) {
//fprintf(stderr, "\"masked_areas\" array must contain numbers\n");
return false;
}
//cc->raw_mask[i / 4][i % 4] = ji->valueint;
rm[i / 4][i % 4] = ji->valueint;
}
if (i % 4 != 0) {
//fprintf(stderr, "\"masked_areas\" array length must be divisable by 4\n");
return false;
}
cc->raw_mask[std::make_pair(w, h)] = rm;
return true;
};
const cJSON *ji = cJSON_GetObjectItem(js, "dcraw_matrix");
if (ji) {
@ -216,24 +276,32 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model)
if (ji) {
if (ji->type != cJSON_Array) {
fprintf(stderr, "\"raw_crop\" must be an array\n");
fprintf(stderr, "invalid entry for raw_crop.\n");
return nullptr;
}
int i;
for (i = 0, ji = ji->child; i < 4 && ji; i++, ji = ji->next) {
if (ji->type != cJSON_Number) {
fprintf(stderr, "\"raw_crop\" array must contain numbers\n");
return nullptr;
} else if (!get_raw_crop(0, 0, ji, cc.get())) {
cJSON *je;
cJSON_ArrayForEach(je, ji) {
if (!cJSON_IsObject(je)) {
fprintf(stderr, "invalid entry for raw_crop.\n");
return nullptr;
} else {
auto js = cJSON_GetObjectItem(je, "frame");
if (!js || js->type != cJSON_Array ||
cJSON_GetArraySize(js) != 2 ||
!cJSON_IsNumber(cJSON_GetArrayItem(js, 0)) ||
!cJSON_IsNumber(cJSON_GetArrayItem(js, 1))) {
fprintf(stderr, "invalid entry for raw_crop.\n");
return nullptr;
}
int w = cJSON_GetArrayItem(js, 0)->valueint;
int h = cJSON_GetArrayItem(js, 1)->valueint;
js = cJSON_GetObjectItem(je, "crop");
if (!js || !get_raw_crop(w, h, js, cc.get())) {
fprintf(stderr, "invalid entry for raw_crop.\n");
return nullptr;
}
}
}
cc->raw_crop[i] = ji->valueint;
}
if (i != 4 || ji) {
fprintf(stderr, "\"raw_crop\" must contain 4 numbers\n");
return nullptr;
}
}
@ -241,24 +309,32 @@ CameraConst* CameraConst::parseEntry(const void *cJSON_, const char *make_model)
if (ji) {
if (ji->type != cJSON_Array) {
fprintf(stderr, "\"masked_areas\" must be an array\n");
fprintf(stderr, "invalid entry for masked_areas.\n");
return nullptr;
}
int i;
for (i = 0, ji = ji->child; i < 2 * 4 && ji; i++, ji = ji->next) {
if (ji->type != cJSON_Number) {
fprintf(stderr, "\"masked_areas\" array must contain numbers\n");
return nullptr;
} else if (!get_masked_areas(0, 0, ji, cc.get())) {
cJSON *je;
cJSON_ArrayForEach(je, ji) {
if (!cJSON_IsObject(je)) {
fprintf(stderr, "invalid entry for masked_areas.\n");
return nullptr;
} else {
auto js = cJSON_GetObjectItem(je, "frame");
if (!js || js->type != cJSON_Array ||
cJSON_GetArraySize(js) != 2 ||
!cJSON_IsNumber(cJSON_GetArrayItem(js, 0)) ||
!cJSON_IsNumber(cJSON_GetArrayItem(js, 1))) {
fprintf(stderr, "invalid entry for masked_areas.\n");
return nullptr;
}
int w = cJSON_GetArrayItem(js, 0)->valueint;
int h = cJSON_GetArrayItem(js, 1)->valueint;
js = cJSON_GetObjectItem(je, "areas");
if (!js || !get_masked_areas(w, h, js, cc.get())) {
fprintf(stderr, "invalid entry for masked_areas.\n");
return nullptr;
}
}
}
cc->raw_mask[i / 4][i % 4] = ji->valueint;
}
if (i % 4 != 0) {
fprintf(stderr, "\"masked_areas\" array length must be divisible by 4\n");
return nullptr;
}
}
@ -399,29 +475,41 @@ void CameraConst::update_pdafOffset(int other)
pdafOffset = other;
}
bool CameraConst::has_rawCrop() const
bool CameraConst::has_rawCrop(int raw_width, int raw_height) const
{
return raw_crop[0] != 0 || raw_crop[1] != 0 || raw_crop[2] != 0 || raw_crop[3] != 0;
return raw_crop.find(std::make_pair(raw_width, raw_height)) != raw_crop.end() || raw_crop.find(std::make_pair(0, 0)) != raw_crop.end();
}
void CameraConst::get_rawCrop(int& left_margin, int& top_margin, int& width, int& height) const
void CameraConst::get_rawCrop(int raw_width, int raw_height, int &left_margin, int &top_margin, int &width, int &height) const
{
left_margin = raw_crop[0];
top_margin = raw_crop[1];
width = raw_crop[2];
height = raw_crop[3];
auto it = raw_crop.find(std::make_pair(raw_width, raw_height));
if (it == raw_crop.end()) {
it = raw_crop.find(std::make_pair(0, 0));
}
if (it != raw_crop.end()) {
left_margin = it->second[0];
top_margin = it->second[1];
width = it->second[2];
height = it->second[3];
} else {
left_margin = top_margin = width = height = 0;
}
}
bool CameraConst::has_rawMask(int idx) const
bool CameraConst::has_rawMask(int raw_width, int raw_height, int idx) const
{
if (idx < 0 || idx > 1) {
return false;
}
return (raw_mask[idx][0] | raw_mask[idx][1] | raw_mask[idx][2] | raw_mask[idx][3]) != 0;
return raw_mask.find(std::make_pair(raw_width, raw_height)) != raw_mask.end() || raw_mask.find(std::make_pair(0, 0)) != raw_mask.end();
}
void CameraConst::get_rawMask(int idx, int& top, int& left, int& bottom, int& right) const
void CameraConst::get_rawMask(int raw_width, int raw_height, int idx, int &top, int &left, int &bottom, int &right) const
{
top = left = bottom = right = 0;
@ -429,10 +517,17 @@ void CameraConst::get_rawMask(int idx, int& top, int& left, int& bottom, int& ri
return;
}
top = raw_mask[idx][0];
left = raw_mask[idx][1];
bottom = raw_mask[idx][2];
right = raw_mask[idx][3];
auto it = raw_mask.find(std::make_pair(raw_width, raw_height));
if (it == raw_mask.end()) {
it = raw_mask.find(std::make_pair(0, 0));
}
if (it != raw_mask.end()) {
top = it->second[idx][0];
left = it->second[idx][1];
bottom = it->second[idx][2];
right = it->second[idx][3];
}
}
void CameraConst::update_Levels(const CameraConst *other)
@ -464,9 +559,7 @@ void CameraConst::update_Crop(CameraConst *other)
return;
}
if (other->has_rawCrop()) {
other->get_rawCrop(raw_crop[0], raw_crop[1], raw_crop[2], raw_crop[3]);
}
raw_crop.insert(other->raw_crop.begin(), other->raw_crop.end());
}
bool CameraConst::get_Levels(camera_const_levels & lvl, int bw, int iso, float fnumber) const

View File

@ -1,9 +1,11 @@
/*
/* -*- C++ -*-
*
* This file is part of RawTherapee.
*/
#pragma once
#include <map>
#include <array>
#include <string>
#include <vector>
@ -17,17 +19,17 @@ class ustring;
namespace rtengine
{
struct camera_const_levels {
int levels[4];
};
class CameraConst final
{
private:
struct camera_const_levels {
int levels[4];
};
std::string make_model;
short dcraw_matrix[12];
int raw_crop[4];
int raw_mask[2][4];
std::map<std::pair<int, int>, std::array<int, 4>> raw_crop;
std::map<std::pair<int, int>, std::array<std::array<int, 4>, 2>> raw_mask;
int white_max;
std::map<int, camera_const_levels> mLevels[2];
std::map<float, float> mApertureScaling;
@ -47,10 +49,10 @@ public:
const short *get_dcrawMatrix(void) const;
const std::vector<int>& get_pdafPattern() const;
int get_pdafOffset() const {return pdafOffset;};
bool has_rawCrop(void) const;
void get_rawCrop(int& left_margin, int& top_margin, int& width, int& height) const;
bool has_rawMask(int idx) const;
void get_rawMask(int idx, int& top, int& left, int& bottom, int& right) const;
bool has_rawCrop(int raw_width, int raw_height) const;
void get_rawCrop(int raw_width, int raw_height, int& left_margin, int& top_margin, int& width, int& height) const;
bool has_rawMask(int raw_width, int raw_height, int idx) const;
void get_rawMask(int raw_width, int raw_height, int idx, int& top, int& left, int& bottom, int& right) const;
int get_BlackLevel(int idx, int iso_speed) const;
int get_WhiteLevel(int idx, int iso_speed, float fnumber) const;
bool has_globalGreenEquilibration() const;
@ -77,4 +79,5 @@ public:
const CameraConst *get(const char make[], const char model[]) const;
};
}
} // namespace rtengine

View File

@ -70,6 +70,14 @@ Examples:
// cropped so the "negative number" way is not totally safe.
"raw_crop": [ 10, 20, 4000, 3000 ],
// multi-aspect support (added 2020-12-03)
// "frame" defines the full dimensions the crop applies to
// (with [0, 0] being the fallback crop if none of the other applies)
"raw_crop" : [
{ "frame" : [4100, 3050], "crop": [10, 20, 4050, 3020] },
{ "frame" : [0, 0], "crop": [10, 20, 4000, 3000] }
]
// Almost same as MaskedAreas DNG tag, used for black level measuring. Here up to two areas can be defined
// by tetrads of numbers:
"masked_areas": [ 51, 2, 3804, 156, 51, 5794, 3804, 5792 ],
@ -84,6 +92,14 @@ Examples:
// instead, to take care of possible light leaks from the light sensing area to the optically black (masked)
// area or sensor imperfections at the outer borders.
// multi-aspect support (added 2020-12-03)
// "frame" defines the full dimensions the masked areas apply to
// (with [0, 0] being the fallback crop if none of the other applies)
"masked_areas" : [
{ "frame" : [4100, 3050], "areas": [10, 20, 4050, 3020] },
{ "frame" : [0, 0], "areas": [10, 20, 4000, 3000] }
]
// list of indices of the rows with on-sensor PDAF pixels, for cameras that have such features. The indices here form a pattern that is repeated for the whole height of the sensor. The values are relative to the "pdaf_offset" value (see below)
"pdaf_pattern" : [ 0,12,36,54,72,90,114,126,144,162,180,204,216,240,252,270,294,306,324,342,366,384,396,414,432,450,474,492,504,522,540,564,576,594,606,630 ],
// index of the first row of the PDAF pattern in the sensor (0 is the topmost row). Allowed to be negative for convenience (this means that the first repetition of the pattern doesn't start from the first row)
@ -1216,16 +1232,28 @@ Camera constants:
{ // Quality C
"make_model": "Canon EOS R5",
"dcraw_matrix" : [9766, -2953, -1254, -4276, 12116, 2433, -437, 1336, 5131],
"raw_crop" : [ 128, 96, 8224, 5490 ],
"masked_areas" : [ 94, 20, 5578, 122 ],
"raw_crop" : [
{ "frame" : [ 8352, 5586 ], "crop" : [ 128, 96, 8224, 5490 ] },
{ "frame" : [ 5248, 3510 ], "crop" : [ 128, 96, 5120, 3382 ] }
],
"masked_areas" : [
{ "frame" : [ 8352, 5586 ], "areas": [ 94, 20, 5578, 122 ] },
{ "frame" : [ 5248, 3510 ], "areas": [ 94, 20, 3510, 122 ] }
],
"ranges" : { "white" : 16382 }
},
{ // Quality C
"make_model": "Canon EOS R6",
"dcraw_matrix" : [8293, -1611, -1132, -4759, 12710, 2275, -1013, 2415, 5508],
"raw_crop": [ 72, 38, 5496, 3670 ],
"masked_areas" : [ 40, 10, 5534, 70 ],
"raw_crop": [
{ "frame": [5568, 3708], "crop" : [ 72, 38, 5496, 3670 ] },
{ "frame": [3584, 2386], "crop" : [ 156, 108, 3404, 2270 ] }
],
"masked_areas" : [
{ "frame": [5568, 3708], "areas": [ 40, 10, 5534, 70 ] },
{ "frame": [3584, 2386], "areas": [ 40, 10, 2374, 110 ] }
],
"ranges" : { "white" : 16382 }
},
@ -1381,7 +1409,11 @@ Camera constants:
{ // Quality C
"make_model": [ "FUJIFILM GFX 100", "FUJIFILM GFX100S" ],
"dcraw_matrix" : [ 16212, -8423, -1583, -4336, 12583, 1937, -195, 726, 6199 ], // taken from ART
"raw_crop": [ 0, 2, 11664, 8734 ]
"raw_crop": [
// multi-aspect crop to account for 16-shot pixel shift images
{ "frame" : [11808, 8754], "crop" : [ 0, 2, 11664, 8734 ] },
{ "frame" : [23616, 17508], "crop" : [ 0, 4, 23328, 17468 ] }
]
},
{ // Quality B

View File

@ -548,11 +548,18 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog
CameraConstantsStore* ccs = CameraConstantsStore::getInstance();
const CameraConst *cc = ccs->get(make, model);
bool raw_crop_cc = false;
int orig_raw_width = width;
int orig_raw_height = height;
if (raw_image) {
if (cc && cc->has_rawCrop()) {
orig_raw_width = raw_width;
orig_raw_height = raw_height;
if (cc && cc->has_rawCrop(raw_width, raw_height)) {
raw_crop_cc = true;
int lm, tm, w, h;
cc->get_rawCrop(lm, tm, w, h);
cc->get_rawCrop(raw_width, raw_height, lm, tm, w, h);
if (isXtrans()) {
shiftXtransMatrix(6 - ((top_margin - tm) % 6), 6 - ((left_margin - lm) % 6));
@ -584,9 +591,9 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog
}
}
if (cc && cc->has_rawMask(0)) {
for (int i = 0; i < 8 && cc->has_rawMask(i); i++) {
cc->get_rawMask(i, mask[i][0], mask[i][1], mask[i][2], mask[i][3]);
if (cc && cc->has_rawMask(orig_raw_width, orig_raw_height, 0)) {
for (int i = 0; i < 2 && cc->has_rawMask(orig_raw_width, orig_raw_height, i); i++) {
cc->get_rawMask(orig_raw_width, orig_raw_height, i, mask[i][0], mask[i][1], mask[i][2], mask[i][3]);
}
}
@ -594,9 +601,10 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog
free(raw_image);
raw_image = nullptr;
} else {
if (get_maker() == "Sigma" && cc && cc->has_rawCrop()) { // foveon images
if (get_maker() == "Sigma" && cc && cc->has_rawCrop(width, height)) { // foveon images
raw_crop_cc = true;
int lm, tm, w, h;
cc->get_rawCrop(lm, tm, w, h);
cc->get_rawCrop(width, height, lm, tm, w, h);
left_margin = lm;
top_margin = tm;
@ -692,11 +700,12 @@ int RawImage::loadRaw(bool loadData, unsigned int imageNum, bool closeFile, Prog
printf("no constants in camconst.json exists for \"%s %s\" (relying only on dcraw defaults)\n", make, model);
}
printf("raw dimensions: %d x %d\n", orig_raw_width, orig_raw_height);
printf("black levels: R:%d G1:%d B:%d G2:%d (%s)\n", get_cblack(0), get_cblack(1), get_cblack(2), get_cblack(3),
black_from_cc ? "provided by camconst.json" : "provided by dcraw");
printf("white levels: R:%d G1:%d B:%d G2:%d (%s)\n", get_white(0), get_white(1), get_white(2), get_white(3),
white_from_cc ? "provided by camconst.json" : "provided by dcraw");
printf("raw crop: %d %d %d %d (provided by %s)\n", left_margin, top_margin, iwidth, iheight, (cc && cc->has_rawCrop()) ? "camconst.json" : "dcraw");
printf("raw crop: %d %d %d %d (provided by %s)\n", left_margin, top_margin, iwidth, iheight, raw_crop_cc ? "camconst.json" : "dcraw");
printf("color matrix provided by %s\n", (cc && cc->has_dcrawMatrix()) ? "camconst.json" : "dcraw");
}
}