1/* sane - Scanner Access Now Easy. 2 3 Copyright (C) 2019 Povilas Kanapickas <povilas@radix.lt> 4 5 This file is part of the SANE package. 6 7 This program is free software; you can redistribute it and/or 8 modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the 10 License, or (at your option) any later version. 11 12 This program is distributed in the hope that it will be useful, but 13 WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program. If not, see <https://www.gnu.org/licenses/>. 19*/ 20 21#define DEBUG_DECLARE_ONLY 22 23#include "image_pipeline.h" 24#include "image.h" 25#include "low.h" 26#include <cmath> 27#include <numeric> 28 29namespace genesys { 30 31ImagePipelineNode::~ImagePipelineNode() {} 32 33bool ImagePipelineNodeCallableSource::get_next_row_data(std::uint8_t* out_data) 34{ 35 bool got_data = producer_(get_row_bytes(), out_data); 36 if (!got_data) 37 eof_ = true; 38 return got_data; 39} 40 41ImagePipelineNodeBufferedCallableSource::ImagePipelineNodeBufferedCallableSource( 42 std::size_t width, std::size_t height, PixelFormat format, std::size_t input_batch_size, 43 ProducerCallback producer) : 44 width_{width}, 45 height_{height}, 46 format_{format}, 47 buffer_{input_batch_size, producer} 48{ 49 buffer_.set_remaining_size(height_ * get_row_bytes()); 50} 51 52bool ImagePipelineNodeBufferedCallableSource::get_next_row_data(std::uint8_t* out_data) 53{ 54 if (curr_row_ >= get_height()) { 55 DBG(DBG_warn, "%s: reading out of bounds. Row %zu, height: %zu\n", __func__, 56 curr_row_, get_height()); 57 eof_ = true; 58 return false; 59 } 60 61 bool got_data = true; 62 63 got_data &= buffer_.get_data(get_row_bytes(), out_data); 64 curr_row_++; 65 if (!got_data) { 66 eof_ = true; 67 } 68 return got_data; 69} 70 71ImagePipelineNodeArraySource::ImagePipelineNodeArraySource(std::size_t width, std::size_t height, 72 PixelFormat format, 73 std::vector<std::uint8_t> data) : 74 width_{width}, 75 height_{height}, 76 format_{format}, 77 data_{std::move(data)}, 78 next_row_{0} 79{ 80 auto size = get_row_bytes() * height_; 81 if (data_.size() < size) { 82 throw SaneException("The given array is too small (%zu bytes). Need at least %zu", 83 data_.size(), size); 84 } 85} 86 87bool ImagePipelineNodeArraySource::get_next_row_data(std::uint8_t* out_data) 88{ 89 if (next_row_ >= height_) { 90 eof_ = true; 91 return false; 92 } 93 94 auto row_bytes = get_row_bytes(); 95 std::memcpy(out_data, data_.data() + row_bytes * next_row_, row_bytes); 96 next_row_++; 97 98 return true; 99} 100 101 102ImagePipelineNodeImageSource::ImagePipelineNodeImageSource(const Image& source) : 103 source_{source} 104{} 105 106bool ImagePipelineNodeImageSource::get_next_row_data(std::uint8_t* out_data) 107{ 108 if (next_row_ >= get_height()) { 109 return false; 110 } 111 std::memcpy(out_data, source_.get_row_ptr(next_row_), get_row_bytes()); 112 next_row_++; 113 return true; 114} 115 116bool ImagePipelineNodeFormatConvert::get_next_row_data(std::uint8_t* out_data) 117{ 118 auto src_format = source_.get_format(); 119 if (src_format == dst_format_) { 120 return source_.get_next_row_data(out_data); 121 } 122 123 buffer_.clear(); 124 buffer_.resize(source_.get_row_bytes()); 125 bool got_data = source_.get_next_row_data(buffer_.data()); 126 127 convert_pixel_row_format(buffer_.data(), src_format, out_data, dst_format_, get_width()); 128 return got_data; 129} 130 131ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source, 132 std::size_t output_width, 133 const std::vector<unsigned>& segment_order, 134 std::size_t segment_pixels, 135 std::size_t interleaved_lines, 136 std::size_t pixels_per_chunk) : 137 source_(source), 138 output_width_{output_width}, 139 segment_order_{segment_order}, 140 segment_pixels_{segment_pixels}, 141 interleaved_lines_{interleaved_lines}, 142 pixels_per_chunk_{pixels_per_chunk}, 143 buffer_{source_.get_row_bytes()} 144{ 145 DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, " 146 "pixels_per_shunk=%zu", segment_order.size(), segment_pixels, 147 interleaved_lines, pixels_per_chunk); 148 149 if (source_.get_height() % interleaved_lines_ > 0) { 150 throw SaneException("Height is not a multiple of the number of lines to interelave %zu/%zu", 151 source_.get_height(), interleaved_lines_); 152 } 153} 154 155ImagePipelineNodeDesegment::ImagePipelineNodeDesegment(ImagePipelineNode& source, 156 std::size_t output_width, 157 std::size_t segment_count, 158 std::size_t segment_pixels, 159 std::size_t interleaved_lines, 160 std::size_t pixels_per_chunk) : 161 source_(source), 162 output_width_{output_width}, 163 segment_pixels_{segment_pixels}, 164 interleaved_lines_{interleaved_lines}, 165 pixels_per_chunk_{pixels_per_chunk}, 166 buffer_{source_.get_row_bytes()} 167{ 168 DBG_HELPER_ARGS(dbg, "segment_count=%zu, segment_size=%zu, interleaved_lines=%zu, " 169 "pixels_per_shunk=%zu", segment_count, segment_pixels, interleaved_lines, 170 pixels_per_chunk); 171 172 segment_order_.resize(segment_count); 173 std::iota(segment_order_.begin(), segment_order_.end(), 0); 174} 175 176bool ImagePipelineNodeDesegment::get_next_row_data(std::uint8_t* out_data) 177{ 178 bool got_data = true; 179 180 buffer_.clear(); 181 for (std::size_t i = 0; i < interleaved_lines_; ++i) { 182 buffer_.push_back(); 183 got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i)); 184 } 185 if (!buffer_.is_linear()) { 186 throw SaneException("Buffer is not linear"); 187 } 188 189 auto format = get_format(); 190 auto segment_count = segment_order_.size(); 191 192 const std::uint8_t* in_data = buffer_.get_row_ptr(0); 193 194 std::size_t groups_count = output_width_ / (segment_order_.size() * pixels_per_chunk_); 195 196 for (std::size_t igroup = 0; igroup < groups_count; ++igroup) { 197 for (std::size_t isegment = 0; isegment < segment_count; ++isegment) { 198 auto input_offset = igroup * pixels_per_chunk_; 199 input_offset += segment_pixels_ * segment_order_[isegment]; 200 auto output_offset = (igroup * segment_count + isegment) * pixels_per_chunk_; 201 202 for (std::size_t ipixel = 0; ipixel < pixels_per_chunk_; ++ipixel) { 203 auto pixel = get_raw_pixel_from_row(in_data, input_offset + ipixel, format); 204 set_raw_pixel_to_row(out_data, output_offset + ipixel, pixel, format); 205 } 206 } 207 } 208 return got_data; 209} 210 211ImagePipelineNodeDeinterleaveLines::ImagePipelineNodeDeinterleaveLines( 212 ImagePipelineNode& source, std::size_t interleaved_lines, std::size_t pixels_per_chunk) : 213 ImagePipelineNodeDesegment(source, source.get_width() * interleaved_lines, 214 interleaved_lines, source.get_width(), 215 interleaved_lines, pixels_per_chunk) 216{} 217 218ImagePipelineNodeSwap16BitEndian::ImagePipelineNodeSwap16BitEndian(ImagePipelineNode& source) : 219 source_(source), 220 needs_swapping_{false} 221{ 222 if (get_pixel_format_depth(source_.get_format()) == 16) { 223 needs_swapping_ = true; 224 } else { 225 DBG(DBG_info, "%s: this pipeline node does nothing for non 16-bit formats", __func__); 226 } 227} 228 229bool ImagePipelineNodeSwap16BitEndian::get_next_row_data(std::uint8_t* out_data) 230{ 231 bool got_data = source_.get_next_row_data(out_data); 232 if (needs_swapping_) { 233 std::size_t pixels = get_row_bytes() / 2; 234 for (std::size_t i = 0; i < pixels; ++i) { 235 std::swap(*out_data, *(out_data + 1)); 236 out_data += 2; 237 } 238 } 239 return got_data; 240} 241 242ImagePipelineNodeInvert::ImagePipelineNodeInvert(ImagePipelineNode& source) : 243 source_(source) 244{ 245} 246 247bool ImagePipelineNodeInvert::get_next_row_data(std::uint8_t* out_data) 248{ 249 bool got_data = source_.get_next_row_data(out_data); 250 auto num_values = get_width() * get_pixel_channels(source_.get_format()); 251 auto depth = get_pixel_format_depth(source_.get_format()); 252 253 switch (depth) { 254 case 16: { 255 auto* data = reinterpret_cast<std::uint16_t*>(out_data); 256 for (std::size_t i = 0; i < num_values; ++i) { 257 *data = 0xffff - *data; 258 data++; 259 } 260 break; 261 } 262 case 8: { 263 auto* data = out_data; 264 for (std::size_t i = 0; i < num_values; ++i) { 265 *data = 0xff - *data; 266 data++; 267 } 268 break; 269 } 270 case 1: { 271 auto* data = out_data; 272 auto num_bytes = (num_values + 7) / 8; 273 for (std::size_t i = 0; i < num_bytes; ++i) { 274 *data = ~*data; 275 data++; 276 } 277 break; 278 } 279 default: 280 throw SaneException("Unsupported pixel depth"); 281 } 282 283 return got_data; 284} 285 286ImagePipelineNodeMergeMonoLinesToColor::ImagePipelineNodeMergeMonoLinesToColor( 287 ImagePipelineNode& source, ColorOrder color_order) : 288 source_(source), 289 buffer_(source_.get_row_bytes()) 290{ 291 DBG_HELPER_ARGS(dbg, "color_order %d", static_cast<unsigned>(color_order)); 292 293 output_format_ = get_output_format(source_.get_format(), color_order); 294} 295 296bool ImagePipelineNodeMergeMonoLinesToColor::get_next_row_data(std::uint8_t* out_data) 297{ 298 bool got_data = true; 299 300 buffer_.clear(); 301 for (unsigned i = 0; i < 3; ++i) { 302 buffer_.push_back(); 303 got_data &= source_.get_next_row_data(buffer_.get_row_ptr(i)); 304 } 305 306 const auto* row0 = buffer_.get_row_ptr(0); 307 const auto* row1 = buffer_.get_row_ptr(1); 308 const auto* row2 = buffer_.get_row_ptr(2); 309 310 auto format = source_.get_format(); 311 312 for (std::size_t x = 0, width = get_width(); x < width; ++x) { 313 std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format); 314 std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 0, format); 315 std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 0, format); 316 set_raw_channel_to_row(out_data, x, 0, ch0, output_format_); 317 set_raw_channel_to_row(out_data, x, 1, ch1, output_format_); 318 set_raw_channel_to_row(out_data, x, 2, ch2, output_format_); 319 } 320 return got_data; 321} 322 323PixelFormat ImagePipelineNodeMergeMonoLinesToColor::get_output_format(PixelFormat input_format, 324 ColorOrder order) 325{ 326 switch (input_format) { 327 case PixelFormat::I1: { 328 if (order == ColorOrder::RGB) { 329 return PixelFormat::RGB111; 330 } 331 break; 332 } 333 case PixelFormat::I8: { 334 if (order == ColorOrder::RGB) { 335 return PixelFormat::RGB888; 336 } 337 if (order == ColorOrder::BGR) { 338 return PixelFormat::BGR888; 339 } 340 break; 341 } 342 case PixelFormat::I16: { 343 if (order == ColorOrder::RGB) { 344 return PixelFormat::RGB161616; 345 } 346 if (order == ColorOrder::BGR) { 347 return PixelFormat::BGR161616; 348 } 349 break; 350 } 351 default: break; 352 } 353 throw SaneException("Unsupported format combidation %d %d", 354 static_cast<unsigned>(input_format), 355 static_cast<unsigned>(order)); 356} 357 358ImagePipelineNodeSplitMonoLines::ImagePipelineNodeSplitMonoLines(ImagePipelineNode& source) : 359 source_(source), 360 next_channel_{0} 361{ 362 output_format_ = get_output_format(source_.get_format()); 363} 364 365bool ImagePipelineNodeSplitMonoLines::get_next_row_data(std::uint8_t* out_data) 366{ 367 bool got_data = true; 368 369 if (next_channel_ == 0) { 370 buffer_.resize(source_.get_row_bytes()); 371 got_data &= source_.get_next_row_data(buffer_.data()); 372 } 373 374 const auto* row = buffer_.data(); 375 auto format = source_.get_format(); 376 377 for (std::size_t x = 0, width = get_width(); x < width; ++x) { 378 std::uint16_t ch = get_raw_channel_from_row(row, x, next_channel_, format); 379 set_raw_channel_to_row(out_data, x, 0, ch, output_format_); 380 } 381 next_channel_ = (next_channel_ + 1) % 3; 382 383 return got_data; 384} 385 386PixelFormat ImagePipelineNodeSplitMonoLines::get_output_format(PixelFormat input_format) 387{ 388 switch (input_format) { 389 case PixelFormat::RGB111: return PixelFormat::I1; 390 case PixelFormat::RGB888: 391 case PixelFormat::BGR888: return PixelFormat::I8; 392 case PixelFormat::RGB161616: 393 case PixelFormat::BGR161616: return PixelFormat::I16; 394 default: break; 395 } 396 throw SaneException("Unsupported input format %d", static_cast<unsigned>(input_format)); 397} 398 399 400ImagePipelineNodeMergeColorToGray::ImagePipelineNodeMergeColorToGray(ImagePipelineNode& source) : 401 source_(source) 402{ 403 404 output_format_ = get_output_format(source_.get_format()); 405 float red_mult = 0.2125f; 406 float green_mult = 0.7154f; 407 float blue_mult = 0.0721f; 408 409 switch (get_pixel_format_color_order(source_.get_format())) { 410 case ColorOrder::RGB: { 411 ch0_mult_ = red_mult; 412 ch1_mult_ = green_mult; 413 ch2_mult_ = blue_mult; 414 break; 415 } 416 case ColorOrder::BGR: { 417 ch0_mult_ = blue_mult; 418 ch1_mult_ = green_mult; 419 ch2_mult_ = red_mult; 420 break; 421 } 422 case ColorOrder::GBR: { 423 ch0_mult_ = green_mult; 424 ch1_mult_ = blue_mult; 425 ch2_mult_ = red_mult; 426 break; 427 } 428 default: 429 throw SaneException("Unknown color order"); 430 } 431 temp_buffer_.resize(source_.get_row_bytes()); 432} 433 434bool ImagePipelineNodeMergeColorToGray::get_next_row_data(std::uint8_t* out_data) 435{ 436 auto* src_data = temp_buffer_.data(); 437 438 bool got_data = source_.get_next_row_data(src_data); 439 440 auto src_format = source_.get_format(); 441 442 for (std::size_t x = 0, width = get_width(); x < width; ++x) { 443 std::uint16_t ch0 = get_raw_channel_from_row(src_data, x, 0, src_format); 444 std::uint16_t ch1 = get_raw_channel_from_row(src_data, x, 1, src_format); 445 std::uint16_t ch2 = get_raw_channel_from_row(src_data, x, 2, src_format); 446 float mono = ch0 * ch0_mult_ + ch1 * ch1_mult_ + ch2 * ch2_mult_; 447 set_raw_channel_to_row(out_data, x, 0, static_cast<std::uint16_t>(mono), output_format_); 448 } 449 return got_data; 450} 451 452PixelFormat ImagePipelineNodeMergeColorToGray::get_output_format(PixelFormat input_format) 453{ 454 switch (input_format) { 455 case PixelFormat::RGB111: 456 return PixelFormat::I1; 457 case PixelFormat::RGB888: 458 case PixelFormat::BGR888: 459 return PixelFormat::I8; 460 case PixelFormat::RGB161616: 461 case PixelFormat::BGR161616: 462 return PixelFormat::I16; 463 default: break; 464 } 465 throw SaneException("Unsupported format %d", static_cast<unsigned>(input_format)); 466} 467 468ImagePipelineNodeComponentShiftLines::ImagePipelineNodeComponentShiftLines( 469 ImagePipelineNode& source, unsigned shift_r, unsigned shift_g, unsigned shift_b) : 470 source_(source), 471 buffer_{source.get_row_bytes()} 472{ 473 DBG_HELPER_ARGS(dbg, "shifts={%d, %d, %d}", shift_r, shift_g, shift_b); 474 475 switch (source.get_format()) { 476 case PixelFormat::RGB111: 477 case PixelFormat::RGB888: 478 case PixelFormat::RGB161616: { 479 channel_shifts_ = { shift_r, shift_g, shift_b }; 480 break; 481 } 482 case PixelFormat::BGR888: 483 case PixelFormat::BGR161616: { 484 channel_shifts_ = { shift_b, shift_g, shift_r }; 485 break; 486 } 487 default: 488 throw SaneException("Unsupported input format %d", 489 static_cast<unsigned>(source.get_format())); 490 } 491 extra_height_ = *std::max_element(channel_shifts_.begin(), channel_shifts_.end()); 492 height_ = source_.get_height(); 493 if (extra_height_ > height_) { 494 height_ = 0; 495 } else { 496 height_ -= extra_height_; 497 } 498} 499 500bool ImagePipelineNodeComponentShiftLines::get_next_row_data(std::uint8_t* out_data) 501{ 502 bool got_data = true; 503 504 if (!buffer_.empty()) { 505 buffer_.pop_front(); 506 } 507 while (buffer_.height() < extra_height_ + 1) { 508 buffer_.push_back(); 509 got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr()); 510 } 511 512 auto format = get_format(); 513 const auto* row0 = buffer_.get_row_ptr(channel_shifts_[0]); 514 const auto* row1 = buffer_.get_row_ptr(channel_shifts_[1]); 515 const auto* row2 = buffer_.get_row_ptr(channel_shifts_[2]); 516 517 for (std::size_t x = 0, width = get_width(); x < width; ++x) { 518 std::uint16_t ch0 = get_raw_channel_from_row(row0, x, 0, format); 519 std::uint16_t ch1 = get_raw_channel_from_row(row1, x, 1, format); 520 std::uint16_t ch2 = get_raw_channel_from_row(row2, x, 2, format); 521 set_raw_channel_to_row(out_data, x, 0, ch0, format); 522 set_raw_channel_to_row(out_data, x, 1, ch1, format); 523 set_raw_channel_to_row(out_data, x, 2, ch2, format); 524 } 525 return got_data; 526} 527 528ImagePipelineNodePixelShiftLines::ImagePipelineNodePixelShiftLines( 529 ImagePipelineNode& source, const std::vector<std::size_t>& shifts) : 530 source_(source), 531 pixel_shifts_{shifts}, 532 buffer_{get_row_bytes()} 533{ 534 extra_height_ = *std::max_element(pixel_shifts_.begin(), pixel_shifts_.end()); 535 height_ = source_.get_height(); 536 if (extra_height_ > height_) { 537 height_ = 0; 538 } else { 539 height_ -= extra_height_; 540 } 541} 542 543bool ImagePipelineNodePixelShiftLines::get_next_row_data(std::uint8_t* out_data) 544{ 545 bool got_data = true; 546 547 if (!buffer_.empty()) { 548 buffer_.pop_front(); 549 } 550 while (buffer_.height() < extra_height_ + 1) { 551 buffer_.push_back(); 552 got_data &= source_.get_next_row_data(buffer_.get_back_row_ptr()); 553 } 554 555 auto format = get_format(); 556 auto shift_count = pixel_shifts_.size(); 557 558 std::vector<std::uint8_t*> rows; 559 rows.resize(shift_count, nullptr); 560 561 for (std::size_t irow = 0; irow < shift_count; ++irow) { 562 rows[irow] = buffer_.get_row_ptr(pixel_shifts_[irow]); 563 } 564 565 for (std::size_t x = 0, width = get_width(); x < width;) { 566 for (std::size_t irow = 0; irow < shift_count && x < width; irow++, x++) { 567 RawPixel pixel = get_raw_pixel_from_row(rows[irow], x, format); 568 set_raw_pixel_to_row(out_data, x, pixel, format); 569 } 570 } 571 return got_data; 572} 573 574ImagePipelineNodePixelShiftColumns::ImagePipelineNodePixelShiftColumns( 575 ImagePipelineNode& source, const std::vector<std::size_t>& shifts) : 576 source_(source), 577 pixel_shifts_{shifts} 578{ 579 width_ = source_.get_width(); 580 extra_width_ = compute_pixel_shift_extra_width(width_, pixel_shifts_); 581 if (extra_width_ > width_) { 582 width_ = 0; 583 } else { 584 width_ -= extra_width_; 585 } 586 temp_buffer_.resize(source_.get_row_bytes()); 587} 588 589bool ImagePipelineNodePixelShiftColumns::get_next_row_data(std::uint8_t* out_data) 590{ 591 if (width_ == 0) { 592 throw SaneException("Attempt to read zero-width line"); 593 } 594 bool got_data = source_.get_next_row_data(temp_buffer_.data()); 595 596 auto format = get_format(); 597 auto shift_count = pixel_shifts_.size(); 598 599 for (std::size_t x = 0, width = get_width(); x < width; x += shift_count) { 600 for (std::size_t ishift = 0; ishift < shift_count && x + ishift < width; ishift++) { 601 RawPixel pixel = get_raw_pixel_from_row(temp_buffer_.data(), x + pixel_shifts_[ishift], 602 format); 603 set_raw_pixel_to_row(out_data, x + ishift, pixel, format); 604 } 605 } 606 return got_data; 607} 608 609 610std::size_t compute_pixel_shift_extra_width(std::size_t source_width, 611 const std::vector<std::size_t>& shifts) 612{ 613 // we iterate across pixel shifts and find the pixel that needs the maximum shift according to 614 // source_width. 615 int group_size = shifts.size(); 616 int non_filled_group = source_width % shifts.size(); 617 int extra_width = 0; 618 619 for (int i = 0; i < group_size; ++i) { 620 int shift_groups = shifts[i] / group_size; 621 int shift_rem = shifts[i] % group_size; 622 623 if (shift_rem < non_filled_group) { 624 shift_groups--; 625 } 626 extra_width = std::max(extra_width, shift_groups * group_size + non_filled_group - i); 627 } 628 return extra_width; 629} 630 631ImagePipelineNodeExtract::ImagePipelineNodeExtract(ImagePipelineNode& source, 632 std::size_t offset_x, std::size_t offset_y, 633 std::size_t width, std::size_t height) : 634 source_(source), 635 offset_x_{offset_x}, 636 offset_y_{offset_y}, 637 width_{width}, 638 height_{height} 639{ 640 cached_line_.resize(source_.get_row_bytes()); 641} 642 643ImagePipelineNodeExtract::~ImagePipelineNodeExtract() {} 644 645ImagePipelineNodeScaleRows::ImagePipelineNodeScaleRows(ImagePipelineNode& source, 646 std::size_t width) : 647 source_(source), 648 width_{width} 649{ 650 cached_line_.resize(source_.get_row_bytes()); 651} 652 653bool ImagePipelineNodeScaleRows::get_next_row_data(std::uint8_t* out_data) 654{ 655 auto src_width = source_.get_width(); 656 auto dst_width = width_; 657 658 bool got_data = source_.get_next_row_data(cached_line_.data()); 659 660 const auto* src_data = cached_line_.data(); 661 auto format = get_format(); 662 auto channels = get_pixel_channels(format); 663 664 if (src_width > dst_width) { 665 // average 666 std::uint32_t counter = src_width / 2; 667 unsigned src_x = 0; 668 for (unsigned dst_x = 0; dst_x < dst_width; dst_x++) { 669 unsigned avg[3] = {0, 0, 0}; 670 unsigned count = 0; 671 while (counter < src_width && src_x < src_width) { 672 counter += dst_width; 673 674 for (unsigned c = 0; c < channels; c++) { 675 avg[c] += get_raw_channel_from_row(src_data, src_x, c, format); 676 } 677 678 src_x++; 679 count++; 680 } 681 counter -= src_width; 682 683 for (unsigned c = 0; c < channels; c++) { 684 set_raw_channel_to_row(out_data, dst_x, c, avg[c] / count, format); 685 } 686 } 687 } else { 688 // interpolate and copy pixels 689 std::uint32_t counter = dst_width / 2; 690 unsigned dst_x = 0; 691 692 for (unsigned src_x = 0; src_x < src_width; src_x++) { 693 unsigned avg[3] = {0, 0, 0}; 694 for (unsigned c = 0; c < channels; c++) { 695 avg[c] += get_raw_channel_from_row(src_data, src_x, c, format); 696 } 697 while ((counter < dst_width || src_x + 1 == src_width) && dst_x < dst_width) { 698 counter += src_width; 699 700 for (unsigned c = 0; c < channels; c++) { 701 set_raw_channel_to_row(out_data, dst_x, c, avg[c], format); 702 } 703 dst_x++; 704 } 705 counter -= dst_width; 706 } 707 } 708 return got_data; 709} 710 711bool ImagePipelineNodeExtract::get_next_row_data(std::uint8_t* out_data) 712{ 713 bool got_data = true; 714 715 while (current_line_ < offset_y_) { 716 got_data &= source_.get_next_row_data(cached_line_.data()); 717 current_line_++; 718 } 719 if (current_line_ >= offset_y_ + source_.get_height()) { 720 std::fill(out_data, out_data + get_row_bytes(), 0); 721 current_line_++; 722 return got_data; 723 } 724 // now we're sure that the following holds: 725 // offset_y_ <= current_line_ < offset_y_ + source_.get_height()) 726 got_data &= source_.get_next_row_data(cached_line_.data()); 727 728 auto format = get_format(); 729 auto x_src_width = source_.get_width() > offset_x_ ? source_.get_width() - offset_x_ : 0; 730 x_src_width = std::min(x_src_width, width_); 731 auto x_pad_after = width_ > x_src_width ? width_ - x_src_width : 0; 732 733 if (get_pixel_format_depth(format) < 8) { 734 // we need to copy pixels one-by-one as there's no per-bit addressing 735 for (std::size_t i = 0; i < x_src_width; ++i) { 736 auto pixel = get_raw_pixel_from_row(cached_line_.data(), i + offset_x_, format); 737 set_raw_pixel_to_row(out_data, i, pixel, format); 738 } 739 for (std::size_t i = 0; i < x_pad_after; ++i) { 740 set_raw_pixel_to_row(out_data, i + x_src_width, RawPixel{}, format); 741 } 742 } else { 743 std::size_t bpp = get_pixel_format_depth(format) / 8; 744 if (x_src_width > 0) { 745 std::memcpy(out_data, cached_line_.data() + offset_x_ * bpp, 746 x_src_width * bpp); 747 } 748 if (x_pad_after > 0) { 749 std::fill(out_data + x_src_width * bpp, 750 out_data + (x_src_width + x_pad_after) * bpp, 0); 751 } 752 } 753 754 current_line_++; 755 756 return got_data; 757} 758 759ImagePipelineNodeCalibrate::ImagePipelineNodeCalibrate(ImagePipelineNode& source, 760 const std::vector<std::uint16_t>& bottom, 761 const std::vector<std::uint16_t>& top, 762 std::size_t x_start) : 763 source_(source) 764{ 765 std::size_t size = 0; 766 if (bottom.size() >= x_start && top.size() >= x_start) { 767 size = std::min(bottom.size() - x_start, top.size() - x_start); 768 } 769 770 offset_.reserve(size); 771 multiplier_.reserve(size); 772 773 for (std::size_t i = 0; i < size; ++i) { 774 offset_.push_back(bottom[i + x_start] / 65535.0f); 775 multiplier_.push_back(65535.0f / (top[i + x_start] - bottom[i + x_start])); 776 } 777} 778 779bool ImagePipelineNodeCalibrate::get_next_row_data(std::uint8_t* out_data) 780{ 781 bool ret = source_.get_next_row_data(out_data); 782 783 auto format = get_format(); 784 auto depth = get_pixel_format_depth(format); 785 std::size_t max_value = 1; 786 switch (depth) { 787 case 8: max_value = 255; break; 788 case 16: max_value = 65535; break; 789 default: 790 throw SaneException("Unsupported depth for calibration %d", depth); 791 } 792 unsigned channels = get_pixel_channels(format); 793 794 std::size_t max_calib_i = offset_.size(); 795 std::size_t curr_calib_i = 0; 796 797 for (std::size_t x = 0, width = get_width(); x < width && curr_calib_i < max_calib_i; ++x) { 798 for (unsigned ch = 0; ch < channels && curr_calib_i < max_calib_i; ++ch) { 799 std::int32_t value = get_raw_channel_from_row(out_data, x, ch, format); 800 801 float value_f = static_cast<float>(value) / max_value; 802 value_f = (value_f - offset_[curr_calib_i]) * multiplier_[curr_calib_i]; 803 value_f = std::round(value_f * max_value); 804 value = clamp<std::int32_t>(static_cast<std::int32_t>(value_f), 0, max_value); 805 set_raw_channel_to_row(out_data, x, ch, value, format); 806 807 curr_calib_i++; 808 } 809 } 810 return ret; 811} 812 813ImagePipelineNodeDebug::ImagePipelineNodeDebug(ImagePipelineNode& source, 814 const std::string& path) : 815 source_(source), 816 path_{path}, 817 buffer_{source_.get_row_bytes()} 818{} 819 820ImagePipelineNodeDebug::~ImagePipelineNodeDebug() 821{ 822 catch_all_exceptions(__func__, [&]() 823 { 824 if (buffer_.empty()) 825 return; 826 827 auto format = get_format(); 828 buffer_.linearize(); 829 write_tiff_file(path_, buffer_.get_front_row_ptr(), get_pixel_format_depth(format), 830 get_pixel_channels(format), get_width(), buffer_.height()); 831 }); 832} 833 834bool ImagePipelineNodeDebug::get_next_row_data(std::uint8_t* out_data) 835{ 836 buffer_.push_back(); 837 bool got_data = source_.get_next_row_data(out_data); 838 std::memcpy(buffer_.get_back_row_ptr(), out_data, get_row_bytes()); 839 return got_data; 840} 841 842std::size_t ImagePipelineStack::get_input_width() const 843{ 844 ensure_node_exists(); 845 return nodes_.front()->get_width(); 846} 847 848std::size_t ImagePipelineStack::get_input_height() const 849{ 850 ensure_node_exists(); 851 return nodes_.front()->get_height(); 852} 853 854PixelFormat ImagePipelineStack::get_input_format() const 855{ 856 ensure_node_exists(); 857 return nodes_.front()->get_format(); 858} 859 860std::size_t ImagePipelineStack::get_input_row_bytes() const 861{ 862 ensure_node_exists(); 863 return nodes_.front()->get_row_bytes(); 864} 865 866std::size_t ImagePipelineStack::get_output_width() const 867{ 868 ensure_node_exists(); 869 return nodes_.back()->get_width(); 870} 871 872std::size_t ImagePipelineStack::get_output_height() const 873{ 874 ensure_node_exists(); 875 return nodes_.back()->get_height(); 876} 877 878PixelFormat ImagePipelineStack::get_output_format() const 879{ 880 ensure_node_exists(); 881 return nodes_.back()->get_format(); 882} 883 884std::size_t ImagePipelineStack::get_output_row_bytes() const 885{ 886 ensure_node_exists(); 887 return nodes_.back()->get_row_bytes(); 888} 889 890void ImagePipelineStack::ensure_node_exists() const 891{ 892 if (nodes_.empty()) { 893 throw SaneException("The pipeline does not contain any nodes"); 894 } 895} 896 897void ImagePipelineStack::clear() 898{ 899 // we need to destroy the nodes back to front, so that the destructors still have valid 900 // references to sources 901 for (auto it = nodes_.rbegin(); it != nodes_.rend(); ++it) { 902 it->reset(); 903 } 904 nodes_.clear(); 905} 906 907std::vector<std::uint8_t> ImagePipelineStack::get_all_data() 908{ 909 auto row_bytes = get_output_row_bytes(); 910 auto height = get_output_height(); 911 912 std::vector<std::uint8_t> ret; 913 ret.resize(row_bytes * height); 914 915 for (std::size_t i = 0; i < height; ++i) { 916 get_next_row_data(ret.data() + row_bytes * i); 917 } 918 return ret; 919} 920 921Image ImagePipelineStack::get_image() 922{ 923 auto height = get_output_height(); 924 925 Image ret; 926 ret.resize(get_output_width(), height, get_output_format()); 927 928 for (std::size_t i = 0; i < height; ++i) { 929 get_next_row_data(ret.get_row_ptr(i)); 930 } 931 return ret; 932} 933 934} // namespace genesys 935