From: hellgore Date: Wed, 8 Aug 2012 13:46:53 +0000 (+0000) Subject: Modified the image_scroll_producer X-Git-Tag: 2.0.4-UNSTABLE~49 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=dfeef1188ebabd5516b11a00219035c3d8b8f078;p=casparcg Modified the image_scroll_producer ================================== * The SPEED parameter is now expressed as pixels to move per frame/field. Previously it was expressed as number of half pixels to move per frame. * The scrolling direction is now reversed so that a positive speed is now correct for an ordinary end credits roll. * Previously both fields in interlaced video formats represented the same temporal image position causing non smooth film like motion. This has now been corrected so that both fields represents two different temporal image positions for smooth field-rate motion instead of frame-rate motion. * The image now starts at a position just outside of the screen and ends at a position just outside of the screen. Previously it started at a strange somewhere in the middle of the image position for end credit rolls. * The SPEED parameter now is a decimal value instead of an integer value. using a value of for example 0.5 will move the image one half pixel each frame/field placed with subpixel accuracy. SPEED 0.5 is the previous equivalent of SPEED -1 since the speed previously was measured in half pixels instead of pixels. * The producer now internally is more exact in the positioning of each frame/field. Previously the image could be placed a little bit "off" because of double precision rounding errors (now calculates offsets in pixels instead of screens, which is more accurate) which could make the image being rendered in between two pixels with subpixel accuracy once in a while even though speed was 2 or more (no half pixels should be involved). * Added support for motion blur via a new BLUR parameter with the number of pixels to blur as parameter. * Added support for premultiplying the color channels of the input image with its alpha, useful when the software used to creates the image saves with straight alpha (caspar expects premultiplied alpha). This is done with the parameter PREMULTIPLY. git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/trunk@3206 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d --- diff --git a/modules/image/image.vcxproj b/modules/image/image.vcxproj index 7cb810d3b..e53995746 100644 --- a/modules/image/image.vcxproj +++ b/modules/image/image.vcxproj @@ -243,7 +243,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 d7a11f65e..37dbf1159 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 @@ -34,20 +37,24 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include +#include using namespace boost::assign; namespace caspar { namespace image { - + struct image_scroll_producer : public core::frame_producer { const std::wstring filename_; @@ -56,21 +63,29 @@ struct image_scroll_producer : public core::frame_producer size_t width_; size_t height_; - int delta_; - int speed_; + double delta_; + double speed_; - std::array start_offset_; + int start_offset_x_; + int start_offset_y_; safe_ptr last_frame_; - explicit image_scroll_producer(const safe_ptr& frame_factory, const std::wstring& filename, int speed) + explicit image_scroll_producer( + const safe_ptr& frame_factory, + const std::wstring& filename, + double speed, + double duration, + int motion_blur_px = 0, + bool premultiply_with_alpha = false) : filename_(filename) , delta_(0) , format_desc_(frame_factory->get_video_format_desc()) , speed_(speed) , last_frame_(core::basic_frame::empty()) { - start_offset_.assign(0.0); + start_offset_x_ = 0; + start_offset_y_ = 0; auto bitmap = load_image(filename_); FreeImage_FlipVertical(bitmap.get()); @@ -78,11 +93,78 @@ struct image_scroll_producer : public core::frame_producer 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()); int 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_); + tweener_t blur_tweener = get_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; @@ -101,19 +183,16 @@ struct image_scroll_producer : public core::frame_producer std::copy_n(bytes, count, frame->image_data().begin() + format_desc_.size - count); count = 0; } - + frame->commit(); frames_.push_back(frame); + + // Set the relative position to the other image fragments + frame->get_frame_transform().fill_translation[1] = - n++; } - - 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; - } + } - else + else if (horizontal) { int i = 0; while(count > 0) @@ -146,53 +225,126 @@ struct image_scroll_producer : public core::frame_producer 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]->get_frame_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->get_frame_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 - virtual safe_ptr receive(int) override - { - delta_ += speed_; - + safe_ptr render_frame(bool allow_eof) + { if(frames_.empty()) return core::basic_frame::eof(); - if(height_ > format_desc_.height) + auto result = make_safe(get_visible()); + auto& fill_translation = result->get_frame_transform().fill_translation; + + if (width_ == format_desc_.width) { - if(static_cast(std::abs(delta_)) >= height_ - format_desc_.height) + if (static_cast(std::abs(delta_)) >= height_ + format_desc_.height && allow_eof) return core::basic_frame::eof(); - for(size_t n = 0; n < frames_.size(); ++n) - { - frames_[n]->get_frame_transform().fill_translation[0] = start_offset_[0]; - frames_[n]->get_frame_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) + if (static_cast(std::abs(delta_)) >= width_ + format_desc_.width && allow_eof) return core::basic_frame::eof(); - for(size_t n = 0; n < frames_.size(); ++n) + fill_translation[0] = + static_cast(start_offset_x_) / static_cast(format_desc_.width) + + (delta_) / static_cast(format_desc_.width); + } + + return result; + } + + safe_ptr render_frame(bool allow_eof, bool advance_delta) + { + auto result = render_frame(allow_eof); + + if (advance_delta) + { + advance(); + } + + return result; + } + + void advance() + { + delta_ += speed_; + } + + virtual safe_ptr receive(int) override + { + if (format_desc_.field_mode == core::field_mode::progressive) + { + return last_frame_ = render_frame(true, true); + } + else + { + auto field1 = render_frame(true, true); + auto field2 = render_frame(true, false); + + if (field1 != core::basic_frame::eof() && field2 == core::basic_frame::eof()) { - frames_[n]->get_frame_transform().fill_translation[0] = start_offset_[0] - (n+1) + delta_ * 0.5/static_cast(format_desc_.width); - frames_[n]->get_frame_transform().fill_translation[1] = start_offset_[1]; + field2 = render_frame(false, true); + } + else + { + advance(); } - } - return last_frame_ = make_safe(frames_); + last_frame_ = field2; + + return core::basic_frame::interlace(field1, field2, format_desc_.field_mode); + } } virtual safe_ptr last_frame() const override @@ -215,16 +367,15 @@ struct image_scroll_producer : public core::frame_producer virtual 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,19 +393,44 @@ safe_ptr create_scroll_producer(const safe_ptr(*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 create_producer_print_proxy( - make_safe(frame_factory, 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(); + + return create_producer_print_proxy(make_safe( + frame_factory, + filename + L"." + *ext, + -speed, + -duration, + motion_blur_px, + premultiply_with_alpha)); } }} \ 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..22845d4a5 --- /dev/null +++ b/modules/image/util/image_algorithms.h @@ -0,0 +1,233 @@ +/* +* 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(caspar::tweener_t& 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, + caspar::tweener_t& tweener) +{ + int 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]); + + /*other_pixel = src.relative(src_iter, -coordinate.first, -coordinate.second); + + if (other_pixel) + 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, + caspar::tweener_t& 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_; +}; + +}}