From b7ab4f3fcc3c557cd956aec6aeeef8d4533f7dbf Mon Sep 17 00:00:00 2001 From: hellgore Date: Tue, 4 Sep 2012 13:18:15 +0000 Subject: [PATCH] Merged image_scroll_producer changes to 2.1 git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches/2.1.0@3229 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d --- common/memory.h | 12 + modules/image/image.vcxproj | 2 + modules/image/image.vcxproj.filters | 6 + .../image/producer/image_scroll_producer.cpp | 293 ++++++++++++++---- modules/image/util/image_algorithms.h | 228 ++++++++++++++ modules/image/util/image_view.h | 277 +++++++++++++++++ 6 files changed, 764 insertions(+), 54 deletions(-) create mode 100644 modules/image/util/image_algorithms.h create mode 100644 modules/image/util/image_view.h diff --git a/common/memory.h b/common/memory.h index c3235240b..5e865078e 100644 --- a/common/memory.h +++ b/common/memory.h @@ -704,6 +704,18 @@ shared_ptr make_shared(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5) return shared_ptr(std::make_shared(std::forward(p0), std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5))); } +template +shared_ptr make_shared(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6) +{ + return shared_ptr(std::make_shared(std::forward(p0), std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6))); +} + +template +shared_ptr make_shared(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7) +{ + return shared_ptr(std::make_shared(std::forward(p0), std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6), std::forward(p7))); +} + template shared_ptr::shared_ptr() : p_(make_shared()) diff --git a/modules/image/image.vcxproj b/modules/image/image.vcxproj index 4051168ec..44d15542f 100644 --- a/modules/image/image.vcxproj +++ b/modules/image/image.vcxproj @@ -134,7 +134,9 @@ + + diff --git a/modules/image/image.vcxproj.filters b/modules/image/image.vcxproj.filters index d5c880138..05665fc73 100644 --- a/modules/image/image.vcxproj.filters +++ b/modules/image/image.vcxproj.filters @@ -47,5 +47,11 @@ source\consumer + + source\util + + + source\util + \ No newline at end of file diff --git a/modules/image/producer/image_scroll_producer.cpp b/modules/image/producer/image_scroll_producer.cpp index 47953da02..0a268bd73 100644 --- a/modules/image/producer/image_scroll_producer.cpp +++ b/modules/image/producer/image_scroll_producer.cpp @@ -17,11 +17,14 @@ * along with CasparCG. If not, see . * * Author: Robert Nagy, ronag89@gmail.com +* Author: Helge Norberg, helge.norberg@svt.se */ #include "image_scroll_producer.h" #include "../util/image_loader.h" +#include "../util/image_view.h" +#include "../util/image_algorithms.h" #include @@ -36,12 +39,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -60,30 +65,110 @@ struct image_scroll_producer : public core::frame_producer_base int width_; int height_; - int delta_; - int speed_; + double delta_; + double speed_; - std::array start_offset_; + int start_offset_x_; + int start_offset_y_; + bool progressive_; - explicit image_scroll_producer(const spl::shared_ptr& frame_factory, const core::video_format_desc& format_desc, const std::wstring& filename, int speed) + explicit image_scroll_producer( + const spl::shared_ptr& frame_factory, + const core::video_format_desc& format_desc, + const std::wstring& filename, + double speed, + double duration, + int motion_blur_px = 0, + bool premultiply_with_alpha = false, + bool progressive = false) : filename_(filename) , delta_(0) , format_desc_(format_desc) , speed_(speed) + , start_offset_x_(0) + , start_offset_y_(0) + , progressive_(progressive) { - start_offset_.assign(0.0); - auto bitmap = load_image(filename_); FreeImage_FlipVertical(bitmap.get()); width_ = FreeImage_GetWidth(bitmap.get()); height_ = FreeImage_GetHeight(bitmap.get()); + bool vertical = width_ == format_desc_.width; + bool horizontal = height_ == format_desc_.height; + + if (!vertical && !horizontal) + BOOST_THROW_EXCEPTION( + caspar::invalid_argument() << msg_info("Neither width nor height matched the video resolution")); + + if (vertical) + { + if (duration != 0.0) + { + double total_num_pixels = format_desc_.height * 2 + height_; + + speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast(format_desc_.field_count)); + + if (std::abs(speed_) > 1.0) + speed_ = std::ceil(speed_); + } + + if (speed_ < 0.0) + { + start_offset_y_ = height_ + format_desc_.height; + } + } + else + { + if (duration != 0.0) + { + double total_num_pixels = format_desc_.width * 2 + width_; + + speed_ = total_num_pixels / (duration * format_desc_.fps * static_cast(format_desc_.field_count)); + + if (std::abs(speed_) > 1.0) + speed_ = std::ceil(speed_); + } + + if (speed_ > 0.0) + start_offset_x_ = format_desc_.width - (width_ % format_desc_.width); + else + start_offset_x_ = format_desc_.width - (width_ % format_desc_.width) + width_ + format_desc_.width; + } + auto bytes = FreeImage_GetBits(bitmap.get()); auto count = width_*height_*4; + image_view original_view(bytes, width_, height_); + + if (premultiply_with_alpha) + premultiply(original_view); + + boost::scoped_array blurred_copy; + + if (motion_blur_px > 0) + { + double angle = 3.14159265 / 2; // Up + + if (horizontal && speed_ < 0) + angle *= 2; // Left + else if (vertical && speed > 0) + angle *= 3; // Down + else if (horizontal && speed > 0) + angle = 0.0; // Right + + blurred_copy.reset(new uint8_t[count]); + image_view blurred_view(blurred_copy.get(), width_, height_); + core::tweener blur_tweener(L"easeInQuad"); + blur(original_view, blurred_view, angle, motion_blur_px, blur_tweener); + bytes = blurred_copy.get(); + bitmap.reset(); + } - if(height_ > format_desc_.height) + if (vertical) { + int n = 1; + while(count > 0) { core::pixel_format_desc desc = core::pixel_format::bgra; @@ -101,18 +186,16 @@ struct image_scroll_producer : public core::frame_producer_base std::copy_n(bytes, count, frame.image_data(0).begin() + format_desc_.size - count); count = 0; } - - frames_.push_back(core::draw_frame(std::move(frame))); - } - - if(speed_ < 0.0) - { - auto offset = format_desc_.height - (height_ % format_desc_.height); - auto offset2 = offset * 0.5/static_cast(format_desc_.height); - start_offset_[1] = (std::ceil(static_cast(height_) / static_cast(format_desc_.height)) + 1.0) * 0.5 - offset2;// - 1.5; + + core::draw_frame draw_frame(std::move(frame)); + + // Set the relative position to the other image fragments + draw_frame.transform().image_transform.fill_translation[1] = - n++; + + frames_.push_back(draw_frame); } } - else + else if (horizontal) { int i = 0; while(count > 0) @@ -143,57 +226,131 @@ struct image_scroll_producer : public core::frame_producer_base std::reverse(frames_.begin(), frames_.end()); - if(speed_ > 0.0) + // Set the relative positions of the image fragments. + for (size_t n = 0; n < frames_.size(); ++n) { - auto offset = format_desc_.width - (width_ % format_desc_.width); - start_offset_[0] = offset * 0.5/static_cast(format_desc_.width); + double translation = - (static_cast(n) + 1.0); + frames_[n].transform().image_transform.fill_translation[0] = translation; + } + } + + CASPAR_LOG(info) << print() << L" Initialized"; + } + + std::vector get_visible() + { + std::vector result; + result.reserve(frames_.size()); + + BOOST_FOREACH(auto& frame, frames_) + { + auto& fill_translation = frame.transform().image_transform.fill_translation; + + if (width_ == format_desc_.width) + { + auto motion_offset_in_screens = (static_cast(start_offset_y_) + delta_) / static_cast(format_desc_.height); + auto vertical_offset = fill_translation[1] + motion_offset_in_screens; + + if (vertical_offset < -1.0 || vertical_offset > 1.0) + { + continue; + } } else { - start_offset_[0] = (std::ceil(static_cast(width_) / static_cast(format_desc_.width)) + 1.0) * 0.5;// - 1.5; + auto motion_offset_in_screens = (static_cast(start_offset_x_) + delta_) / static_cast(format_desc_.width); + auto horizontal_offset = fill_translation[0] + motion_offset_in_screens; + + if (horizontal_offset < -1.0 || horizontal_offset > 1.0) + { + continue; + } } + + result.push_back(frame); } - CASPAR_LOG(info) << print() << L" Initialized"; + return std::move(result); } // frame_producer - - core::draw_frame receive_impl() override - { - delta_ += speed_; - + core::draw_frame render_frame(bool allow_eof) + { if(frames_.empty()) - return core::draw_frame::late(); + return core::draw_frame::empty(); - if(height_ > format_desc_.height) + core::draw_frame result(get_visible()); + auto& fill_translation = result.transform().image_transform.fill_translation; + + if (width_ == format_desc_.width) { - if(static_cast(std::abs(delta_)) >= height_ - format_desc_.height) - return last_frame(); + if (static_cast(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof) + return core::draw_frame::empty(); - for(int n = 0; n < frames_.size(); ++n) - { - frames_[n].transform().image_transform.fill_translation[0] = start_offset_[0]; - frames_[n].transform().image_transform.fill_translation[1] = start_offset_[1] - (n+1) + delta_ * 0.5/static_cast(format_desc_.height); - } + fill_translation[1] = + static_cast(start_offset_y_) / static_cast(format_desc_.height) + + delta_ / static_cast(format_desc_.height); + } + else + { + if (static_cast(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof) + return core::draw_frame::empty(); + + fill_translation[0] = + static_cast(start_offset_x_) / static_cast(format_desc_.width) + + (delta_) / static_cast(format_desc_.width); + } + + return result; + } + + core::draw_frame render_frame(bool allow_eof, bool advance_delta) + { + auto result = render_frame(allow_eof); + + if (advance_delta) + { + advance(); + } + + return result; + } + + void advance() + { + delta_ += speed_; + } + + core::draw_frame receive_impl() override + { + core::draw_frame result; + + if (format_desc_.field_mode == core::field_mode::progressive || progressive_) + { + result = render_frame(true, true); } else { - if(static_cast(std::abs(delta_)) >= width_ - format_desc_.width) - return last_frame(); + auto field1 = render_frame(true, true); + auto field2 = render_frame(true, false); - for(int n = 0; n < frames_.size(); ++n) + if (field1 != core::draw_frame::empty() && field2 == core::draw_frame::empty()) { - frames_[n].transform().image_transform.fill_translation[0] = start_offset_[0] - (n+1) + delta_ * 0.5/static_cast(format_desc_.width); - frames_[n].transform().image_transform.fill_translation[1] = start_offset_[1]; + field2 = render_frame(false, true); } + else + { + advance(); + } + + result = core::draw_frame::interlace(field1, field2, format_desc_.field_mode); } event_subject_ << monitor::event("file/path") % filename_ << monitor::event("delta") % delta_ << monitor::event("speed") % speed_; - return core::draw_frame(frames_); + return result; } std::wstring print() const override @@ -216,16 +373,15 @@ struct image_scroll_producer : public core::frame_producer_base uint32_t nb_frames() const override { - if(height_ > format_desc_.height) + if(width_ == format_desc_.width) { - auto length = (height_ - format_desc_.height); - return length/std::abs(speed_);// + length % std::abs(delta_)); + auto length = (height_ + format_desc_.height * 2); + return static_cast(length / std::abs(speed_));// + length % std::abs(delta_)); } else { - auto length = (width_ - format_desc_.width); - auto result = length/std::abs(speed_);// + length % std::abs(delta_)); - return result; + auto length = (width_ + format_desc_.width * 2); + return static_cast(length / std::abs(speed_));// + length % std::abs(delta_)); } } @@ -242,29 +398,58 @@ struct image_scroll_producer : public core::frame_producer_base spl::shared_ptr create_scroll_producer(const spl::shared_ptr& frame_factory, const core::video_format_desc& format_desc, const std::vector& params) { - static const std::vector extensions = list_of(L"png")(L"tga")(L"bmp")(L"jpg")(L"jpeg")(L"gif")(L"tiff")(L"tif")(L"jp2")(L"jpx")(L"j2k")(L"j2c"); + static const std::vector extensions = list_of(L".png")(L".tga")(L".bmp")(L".jpg")(L".jpeg")(L".gif")(L".tiff")(L".tif")(L".jp2")(L".jpx")(L".j2k")(L".j2c"); std::wstring filename = env::media_folder() + L"\\" + params[0]; auto ext = std::find_if(extensions.begin(), extensions.end(), [&](const std::wstring& ex) -> bool { - return boost::filesystem::is_regular_file(boost::filesystem::wpath(filename).replace_extension(ex)); + return boost::filesystem::is_regular_file(boost::filesystem::path(filename).replace_extension(ex)); }); if(ext == extensions.end()) return core::frame_producer::empty(); - int speed = 0; + double speed = 0.0; + double duration = 0.0; auto speed_it = std::find(params.begin(), params.end(), L"SPEED"); if(speed_it != params.end()) { if(++speed_it != params.end()) - speed = boost::lexical_cast(*speed_it); + speed = boost::lexical_cast(*speed_it); + } + + if (speed == 0) + { + auto duration_it = std::find(params.begin(), params.end(), L"DURATION"); + + if (duration_it != params.end() && ++duration_it != params.end()) + { + duration = boost::lexical_cast(*duration_it); + } } - if(speed == 0) + if(speed == 0 && duration == 0) return core::frame_producer::empty(); - return spl::make_shared(frame_factory, format_desc, filename + L"." + *ext, speed); + int motion_blur_px = 0; + auto blur_it = std::find(params.begin(), params.end(), L"BLUR"); + if (blur_it != params.end() && ++blur_it != params.end()) + { + motion_blur_px = boost::lexical_cast(*blur_it); + } + + bool premultiply_with_alpha = std::find(params.begin(), params.end(), L"PREMULTIPLY") != params.end(); + bool progressive = std::find(params.begin(), params.end(), L"PROGRESSIVE") != params.end(); + + return core::create_destroy_proxy(spl::make_shared( + frame_factory, + format_desc, + filename + *ext, + -speed, + -duration, + motion_blur_px, + premultiply_with_alpha, + progressive)); } }} \ No newline at end of file diff --git a/modules/image/util/image_algorithms.h b/modules/image/util/image_algorithms.h new file mode 100644 index 000000000..b7b38d285 --- /dev/null +++ b/modules/image/util/image_algorithms.h @@ -0,0 +1,228 @@ +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . +* +* Author: Helge Norberg, helge.norberg@svt.se +*/ + +#pragma once + +#include + +#include +#include + +namespace caspar { namespace image { + +/** + * Helper for calculating the color of a pixel given any number of of other + * pixels (each with their own weight). + */ +class rgba_weighting +{ + int r, g, b, a; + int total_weight; +public: + rgba_weighting() + : r(0), g(0), b(0), a(0), total_weight(0) + { + } + + template + inline void add_pixel(const RGBAPixel& pixel, uint8_t weight) + { + r += pixel.r() * weight; + g += pixel.g() * weight; + b += pixel.b() * weight; + a += pixel.a() * weight; + + total_weight += weight; + } + + template + inline void store_result(RGBAPixel& pixel) + { + pixel.r() = static_cast(r / total_weight); + pixel.g() = static_cast(g / total_weight); + pixel.b() = static_cast(b / total_weight); + pixel.a() = static_cast(a / total_weight); + } +}; + +template +std::vector get_tweened_values(const core::tweener& tweener, size_t num_values, T from, T to) +{ + std::vector result; + result.reserve(num_values); + + double start = static_cast(from); + double delta = static_cast(to - from); + double duration = static_cast(num_values); + + for (double t = 0; t < duration; ++t) + { + result.push_back(static_cast(tweener(t, start, delta, duration - 1.0))); + } + + return std::move(result); +} + +/** + * Blur a source image and store the blurred result in a destination image. + *

+ * The blur is done by weighting each relative pixel from a destination pixel + * position using a vector of relative x-y pairs. The further away a related + * pixel is the less weight it gets. A tweener is used to calculate the actual + * weights of each related pixel. + * + * @param src The source view. Has to model the ImageView + * concept and have a pixel type modelling the + * RGBAPixel concept. + * @param dst The destination view. Has to model the + * ImageView concept and have a pixel type + * modelling the RGBAPixel concept. + * @param motion_trail_coordinates The relative x-y positions to weight in for + * each pixel. + * @param tweener The tweener to use for calculating the + * weights of each relative position in the + * motion trail. + */ +template +void blur( + const SrcView& src, + DstView& dst, + const std::vector> motion_trail_coordinates, + const core::tweener& tweener) +{ + auto blur_px = motion_trail_coordinates.size(); + auto tweened_weights_y = get_tweened_values(tweener, blur_px + 2, 255, 0); + tweened_weights_y.pop_back(); + tweened_weights_y.erase(tweened_weights_y.begin()); + + auto src_end = src.end(); + auto dst_iter = dst.begin(); + + for (auto src_iter = src.begin(); src_iter != src_end; ++src_iter, ++dst_iter) + { + rgba_weighting w; + + for (int i = 0; i < blur_px; ++i) + { + auto& coordinate = motion_trail_coordinates[i]; + auto other_pixel = src.relative(src_iter, coordinate.first, coordinate.second); + + if (other_pixel == nullptr) + break; + + w.add_pixel(*other_pixel, tweened_weights_y[i]); + } + + w.add_pixel(*src_iter, 255); + w.store_result(*dst_iter); + } +} + +/** + * Calculate relative x-y coordinates of a straight line with a given angle and + * a given number of points. + * + * @param num_pixels The number of pixels/points to create. + * @param angle_radians The angle of the line in radians. + * + * @return the x-y pairs. + */ +std::vector> get_line_points(int num_pixels, double angle_radians) +{ + std::vector> line_points; + line_points.reserve(num_pixels); + + double delta_x = std::cos(angle_radians); + double delta_y = -std::sin(angle_radians); // In memory is revered + double max_delta = std::max(std::abs(delta_x), std::abs(delta_y)); + double amplification = 1.0 / max_delta; + delta_x *= amplification; + delta_y *= amplification; + + for (int i = 1; i <= num_pixels; ++i) + line_points.push_back(std::make_pair( + static_cast(std::floor(delta_x * static_cast(i) + 0.5)), + static_cast(std::floor(delta_y * static_cast(i) + 0.5)))); + + return std::move(line_points); +} + +/** + * Directionally blur a source image modelling the ImageView concept and store + * the blurred image to a destination image also modelling the ImageView + * concept. + *

+ * The pixel type of the views must model the RGBAPixel concept. + * + * @param src The source image view. Has to model the ImageView + * concept and have a pixel type that models RGBAPixel. + * @param dst The destiation image view. Has to model the ImageView + * concept and have a pixel type that models RGBAPixel. + * @param angle_radians The angle in radians to directionally blur the image. + * @param blur_px The number of pixels of the blur. + * @param tweener The tweener to use to create a pixel weighting curve + * with. + */ +template +void blur( + const SrcView& src, + DstView& dst, + double angle_radians, + int blur_px, + const core::tweener& tweener) +{ + auto motion_trail = get_line_points(blur_px, angle_radians); + + blur(src, dst, motion_trail, tweener); +} + +/** + * Premultiply with alpha for each pixel in an ImageView. The modifications is + * done in place. The pixel type of the ImageView must model the RGBAPixel + * concept. + * + * @param view_to_modify The image view to premultiply in place. Has to model + * the ImageView concept and have a pixel type that + * models RGBAPixel. + */ +template +void premultiply(SrcDstView& view_to_modify) +{ + std::for_each(view_to_modify.begin(), view_to_modify.end(), [&](SrcDstView::pixel_type& pixel) + { + int alpha = static_cast(pixel.a()); + + if (alpha != 255) // Performance optimization + { + // We don't event try to premultiply 0 since it will be unaffected. + if (pixel.r()) + pixel.r() = static_cast(static_cast(pixel.r()) * alpha / 255); + + if (pixel.g()) + pixel.g() = static_cast(static_cast(pixel.g()) * alpha / 255); + + if (pixel.b()) + pixel.b() = static_cast(static_cast(pixel.b()) * alpha / 255); + } + }); +} + +}} diff --git a/modules/image/util/image_view.h b/modules/image/util/image_view.h new file mode 100644 index 000000000..422ab3c1e --- /dev/null +++ b/modules/image/util/image_view.h @@ -0,0 +1,277 @@ +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . +* +* Author: Helge Norberg, helge.norberg@svt.se +*/ + +#pragma once + +#include + +namespace caspar { namespace image { + +/** + * A POD pixel with a compatible memory layout as a 8bit BGRA pixel (32bits in + * total). + *

+ * Models the PackedPixel concept used by for example image_view. Also models + * the RGBAPixel concept which does not care about the order between RGBA but + * only requires that all 4 channel has accessors. + */ +class bgra_pixel +{ + uint8_t b_; + uint8_t g_; + uint8_t r_; + uint8_t a_; +public: + bgra_pixel(uint8_t b = 0, uint8_t g = 0, uint8_t r = 0, uint8_t a = 0) : b_(b), g_(g), r_(r), a_(a) {} + inline const uint8_t& b() const { return b_; } + inline uint8_t& b() { return b_; } + inline const uint8_t& g() const { return g_; } + inline uint8_t& g() { return g_; } + inline const uint8_t& r() const { return r_; } + inline uint8_t& r() { return r_; } + inline const uint8_t& a() const { return a_; } + inline uint8_t& a() { return a_; } +}; + +template class image_sub_view; + +/** + * An image view abstracting raw packed pixel data + *

+ * This is only a view, it does not own the data. + *

+ * Models the the ImageView concept. + */ +template +class image_view +{ +public: + typedef PackedPixel pixel_type; + + image_view(void* raw_start, int width, int height) + : begin_(static_cast(raw_start)) + , end_(begin_ + (width * height)) + , width_(width) + , height_(height) + { + } + + PackedPixel* begin() + { + return begin_; + } + + const PackedPixel* begin() const + { + return begin_; + } + + PackedPixel* end() + { + return end_; + } + + const PackedPixel* end() const + { + return end_; + } + + template + inline PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) + { + auto pixel_distance = delta_x + width_ * delta_y; + PackedPixel* to_address = &(*to); + auto result = to_address + pixel_distance; + + if (result < begin_ || result >= end_) + return nullptr; + else + return result; + } + + template + inline const PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) const + { + //auto x_distance + auto pixel_distance = delta_x + width_ * delta_y; + const PackedPixel* to_address = &(*to); + auto result = to_address + pixel_distance; + + /*if (delta_x != 0) + { + auto actual_delta_y = result % width_ + }*/ + + if (result < begin_ || result >= end_) + return nullptr; + else + return result; + } + + int width() const + { + return width_; + } + + int height() const + { + return height_; + } + + image_sub_view subview(int x, int y, int width, int height) + { + return image_sub_view(*this, x, y, width, height); + } + + const image_sub_view subview(int x, int y, int width, int height) const + { + return image_sub_view(*this, x, y, width, height); + } +private: + PackedPixel* begin_; + PackedPixel* end_; + int width_; + int height_; +}; + +template +class is_within_view +{ +public: + is_within_view(const PackedPixel* begin, int width, int stride) + : begin_(begin) + , width_(width) + , stride_(stride) + , no_check_(width == stride) + { + } + + inline bool operator()(const PackedPixel& pixel) const + { + if (no_check_) + return true; + + const PackedPixel* position = &pixel; + int distance_from_row_start = (position - begin_) % stride_; + + return distance_from_row_start < width_; + } +private: + const PackedPixel* begin_; + int width_; + int stride_; + bool no_check_; +}; + +template +struct image_stride_iterator : public boost::filter_iterator, PackedPixel*> +{ + image_stride_iterator(PackedPixel* begin, PackedPixel* end, int width, int stride) + : boost::filter_iterator, PackedPixel*>::filter_iterator( + is_within_view(begin, width, stride), begin, end) + { + } +}; + +/** + * A sub view created from an image_view. + *

+ * This also models the ImageView concept. + */ +template +class image_sub_view +{ +public: + typedef PackedPixel pixel_type; + + image_sub_view(image_view& root_view, int x, int y, int width, int height) + : root_view_(root_view) + , relative_to_root_x_(x) + , relative_to_root_y_(y) + , width_(width) + , height_(height) + , raw_begin_(root_view.relative(root_view.begin(), x, y)) + , raw_end_(root_view.relative(raw_begin_, width - 1, height_ - 1) + 1) + { + } + + image_stride_iterator begin() + { + return image_stride_iterator(raw_begin_, raw_end_, width_, root_view_.width()); + } + + image_stride_iterator begin() const + { + return image_stride_iterator(raw_begin_, raw_end_, width_, root_view_.width()); + } + + image_stride_iterator end() + { + return image_stride_iterator(raw_end_, raw_end_, width_, root_view_.width()); + } + + image_stride_iterator end() const + { + return image_stride_iterator(raw_end_, raw_end_, width_, root_view_.width()); + } + + template + PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) + { + return root_view_.relative(to, delta_x, delta_y); + } + + template + const PackedPixel* relative(PackedPixelIter to, int delta_x, int delta_y) const + { + return root_view_.relative(to, delta_x, delta_y); + } + + int width() const + { + return width_; + } + + int height() const + { + return height_; + } + + image_sub_view subview(int x, int y, int width, int height) + { + return root_view_.subview(relative_to_root_x_ + x, relative_to_root_y_ + y, width, height); + } + + const image_sub_view subview(int x, int y, int width, int height) const + { + return root_view_.subview(relative_to_root_x_ + x, relative_to_root_y_ + y, width, height); + } +private: + image_view root_view_; + int relative_to_root_x_; + int relative_to_root_y_; + int width_; + int height_; + PackedPixel* raw_begin_; + PackedPixel* raw_end_; +}; + +}} -- 2.39.2