C++11 and whitespace cleanups.
[dcpomatic.git] / src / lib / image.cc
index 4859ebe14876320718ae162f65c2361c5817b8ad..1adcabc065a2f1d3293c504a7c5df001cb05e0fc 100644 (file)
@@ -64,8 +64,8 @@ using std::string;
 using dcp::Size;
 
 
-/** The memory alignment, in bytes, used for each row of an image if aligment is requested */
-#define ALIGNMENT 64
+/** The memory alignment, in bytes, used for each row of an image if Alignment::PADDED is requested */
+int constexpr ALIGNMENT = 64;
 
 /* U/V black value for 8-bit colour */
 static uint8_t const eight_bit_uv =    (1 << 7) - 1;
@@ -107,6 +107,7 @@ Image::horizontal_factor (int n) const
        return lrintf(powf(2.0f, d->log2_chroma_w));
 }
 
+
 /** @param n Component index.
  *  @return Number of samples (i.e. pixels, unless sub-sampled) in each direction for this component.
  */
@@ -119,19 +120,20 @@ Image::sample_size (int n) const
                );
 }
 
+
 /** @return Number of planes */
 int
 Image::planes () const
 {
+       if (_pixel_format == AV_PIX_FMT_PAL8) {
+               return 2;
+       }
+
        auto d = av_pix_fmt_desc_get(_pixel_format);
        if (!d) {
                throw PixelFormatError ("planes()", _pixel_format);
        }
 
-       if (_pixel_format == AV_PIX_FMT_PAL8) {
-               return 2;
-       }
-
        if ((d->flags & AV_PIX_FMT_FLAG_PLANAR) == 0) {
                return 1;
        }
@@ -177,19 +179,19 @@ Image::crop_scale_window (
        VideoRange video_range,
        AVPixelFormat out_format,
        VideoRange out_video_range,
-       bool out_aligned,
+       Alignment out_alignment,
        bool fast
        ) const
 {
        /* Empirical testing suggests that sws_scale() will crash if
-          the input image is not aligned.
+          the input image is not padded.
        */
-       DCPOMATIC_ASSERT (aligned ());
+       DCPOMATIC_ASSERT (alignment() == Alignment::PADDED);
 
        DCPOMATIC_ASSERT (out_size.width >= inter_size.width);
        DCPOMATIC_ASSERT (out_size.height >= inter_size.height);
 
-       auto out = make_shared<Image>(out_format, out_size, out_aligned);
+       auto out = make_shared<Image>(out_format, out_size, out_alignment);
        out->make_black ();
 
        auto in_desc = av_pix_fmt_desc_get (_pixel_format);
@@ -297,31 +299,42 @@ Image::crop_scale_window (
                out->make_part_black (corner.x + cropped_size.width, out_size.width - cropped_size.width);
        }
 
+       if (
+               video_range == VideoRange::VIDEO &&
+               out_video_range == VideoRange::FULL &&
+               av_pix_fmt_desc_get(_pixel_format)->flags & AV_PIX_FMT_FLAG_RGB
+          ) {
+               /* libswscale will not convert video range for RGB sources, so we have to do it ourselves */
+               out->video_range_to_full_range ();
+       }
+
        return out;
 }
 
+
 shared_ptr<Image>
-Image::convert_pixel_format (dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool out_aligned, bool fast) const
+Image::convert_pixel_format (dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, Alignment out_alignment, bool fast) const
 {
-       return scale(size(), yuv_to_rgb, out_format, out_aligned, fast);
+       return scale(size(), yuv_to_rgb, out_format, out_alignment, fast);
 }
 
+
 /** @param out_size Size to scale to.
  *  @param yuv_to_rgb YUVToRGB transform transform to use, if required.
  *  @param out_format Output pixel format.
- *  @param out_aligned true to make an aligned output image.
+ *  @param out_aligment Output alignment.
  *  @param fast Try to be fast at the possible expense of quality; at present this means using
  *  fast bilinear rather than bicubic scaling.
  */
 shared_ptr<Image>
-Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool out_aligned, bool fast) const
+Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, Alignment out_alignment, bool fast) const
 {
        /* Empirical testing suggests that sws_scale() will crash if
-          the input image is not aligned.
+          the input image alignment is not PADDED.
        */
-       DCPOMATIC_ASSERT (aligned ());
+       DCPOMATIC_ASSERT (alignment() == Alignment::PADDED);
 
-       auto scaled = make_shared<Image>(out_format, out_size, out_aligned);
+       auto scaled = make_shared<Image>(out_format, out_size, out_alignment);
        auto scale_context = sws_getContext (
                size().width, size().height, pixel_format(),
                out_size.width, out_size.height, out_format,
@@ -364,6 +377,7 @@ Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_fo
        return scaled;
 }
 
+
 /** Blacken a YUV image whose bits per pixel is rounded up to 16 */
 void
 Image::yuv_16_black (uint16_t v, bool alpha)
@@ -386,12 +400,14 @@ Image::yuv_16_black (uint16_t v, bool alpha)
        }
 }
 
+
 uint16_t
 Image::swap_16 (uint16_t v)
 {
        return ((v >> 8) & 0xff) | ((v & 0xff) << 8);
 }
 
+
 void
 Image::make_part_black (int const start, int const width)
 {
@@ -462,6 +478,7 @@ Image::make_part_black (int const start, int const width)
        }
 }
 
+
 void
 Image::make_black ()
 {
@@ -577,6 +594,7 @@ Image::make_black ()
        }
 }
 
+
 void
 Image::make_transparent ()
 {
@@ -587,6 +605,7 @@ Image::make_transparent ()
        memset (data()[0], 0, sample_size(0).height * stride()[0]);
 }
 
+
 void
 Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
 {
@@ -727,7 +746,7 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
        case AV_PIX_FMT_YUV420P:
        {
-               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, false);
+               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false);
                dcp::Size const ts = size();
                dcp::Size const os = yuv->size();
                for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) {
@@ -762,7 +781,7 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
        case AV_PIX_FMT_YUV420P10:
        {
-               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, false);
+               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false);
                dcp::Size const ts = size();
                dcp::Size const os = yuv->size();
                for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) {
@@ -797,7 +816,7 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
        case AV_PIX_FMT_YUV422P10LE:
        {
-               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, false);
+               auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, Alignment::COMPACT, false);
                dcp::Size const ts = size();
                dcp::Size const os = yuv->size();
                for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) {
@@ -833,6 +852,7 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
 }
 
+
 void
 Image::copy (shared_ptr<const Image> other, Position<int> position)
 {
@@ -848,6 +868,7 @@ Image::copy (shared_ptr<const Image> other, Position<int> position)
        }
 }
 
+
 void
 Image::read_from_socket (shared_ptr<Socket> socket)
 {
@@ -861,6 +882,7 @@ Image::read_from_socket (shared_ptr<Socket> socket)
        }
 }
 
+
 void
 Image::write_to_socket (shared_ptr<Socket> socket) const
 {
@@ -874,6 +896,7 @@ Image::write_to_socket (shared_ptr<Socket> socket) const
        }
 }
 
+
 float
 Image::bytes_per_pixel (int c) const
 {
@@ -920,21 +943,23 @@ Image::bytes_per_pixel (int c) const
        return bpp[c];
 }
 
+
 /** Construct a Image of a given size and format, allocating memory
  *  as required.
  *
  *  @param p Pixel format.
  *  @param s Size in pixels.
- *  @param aligned true to make each row of this image aligned to a ALIGNMENT-byte boundary.
+ *  @param alignment PADDED to make each row of this image aligned to a ALIGNMENT-byte boundary, otherwise COMPACT.
  */
-Image::Image (AVPixelFormat p, dcp::Size s, bool aligned)
+Image::Image (AVPixelFormat p, dcp::Size s, Alignment alignment)
        : _size (s)
        , _pixel_format (p)
-       , _aligned (aligned)
+       , _alignment (alignment)
 {
        allocate ();
 }
 
+
 void
 Image::allocate ()
 {
@@ -949,7 +974,7 @@ Image::allocate ()
 
        for (int i = 0; i < planes(); ++i) {
                _line_size[i] = ceil (_size.width * bytes_per_pixel(i));
-               _stride[i] = stride_round_up (i, _line_size, _aligned ? ALIGNMENT : 1);
+               _stride[i] = stride_round_up (i, _line_size, _alignment == Alignment::PADDED ? ALIGNMENT : 1);
 
                /* The assembler function ff_rgb24ToY_avx (in libswscale/x86/input.asm)
                   uses a 16-byte fetch to read three bytes (R/G/B) of image data.
@@ -998,11 +1023,12 @@ Image::allocate ()
        }
 }
 
+
 Image::Image (Image const & other)
        : std::enable_shared_from_this<Image>(other)
        , _size (other._size)
        , _pixel_format (other._pixel_format)
-       , _aligned (other._aligned)
+       , _alignment (other._alignment)
 {
        allocate ();
 
@@ -1018,10 +1044,11 @@ Image::Image (Image const & other)
        }
 }
 
-Image::Image (AVFrame const * frame)
+
+Image::Image (AVFrame const * frame, Alignment alignment)
        : _size (frame->width, frame->height)
        , _pixel_format (static_cast<AVPixelFormat>(frame->format))
-       , _aligned (true)
+       , _alignment (alignment)
 {
        DCPOMATIC_ASSERT (_pixel_format != AV_PIX_FMT_NONE);
 
@@ -1040,10 +1067,11 @@ Image::Image (AVFrame const * frame)
        }
 }
 
-Image::Image (shared_ptr<const Image> other, bool aligned)
+
+Image::Image (shared_ptr<const Image> other, Alignment alignment)
        : _size (other->_size)
        , _pixel_format (other->_pixel_format)
-       , _aligned (aligned)
+       , _alignment (alignment)
 {
        allocate ();
 
@@ -1060,6 +1088,7 @@ Image::Image (shared_ptr<const Image> other, bool aligned)
        }
 }
 
+
 Image&
 Image::operator= (Image const & other)
 {
@@ -1072,6 +1101,7 @@ Image::operator= (Image const & other)
        return *this;
 }
 
+
 void
 Image::swap (Image & other)
 {
@@ -1084,9 +1114,10 @@ Image::swap (Image & other)
                std::swap (_stride[i], other._stride[i]);
        }
 
-       std::swap (_aligned, other._aligned);
+       std::swap (_alignment, other._alignment);
 }
 
+
 Image::~Image ()
 {
        for (int i = 0; i < planes(); ++i) {
@@ -1098,46 +1129,52 @@ Image::~Image ()
        av_free (_stride);
 }
 
+
 uint8_t * const *
 Image::data () const
 {
        return _data;
 }
 
+
 int const *
 Image::line_size () const
 {
        return _line_size;
 }
 
+
 int const *
 Image::stride () const
 {
        return _stride;
 }
 
+
 dcp::Size
 Image::size () const
 {
        return _size;
 }
 
-bool
-Image::aligned () const
+
+Image::Alignment
+Image::alignment () const
 {
-       return _aligned;
+       return _alignment;
 }
 
 
 PositionImage
-merge (list<PositionImage> images)
+merge (list<PositionImage> images, Image::Alignment alignment)
 {
        if (images.empty ()) {
                return {};
        }
 
        if (images.size() == 1) {
-               return images.front ();
+               images.front().image = Image::ensure_alignment(images.front().image, alignment);
+               return images.front();
        }
 
        dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height);
@@ -1145,7 +1182,7 @@ merge (list<PositionImage> images)
                all.extend (dcpomatic::Rect<int>(i.position, i.image->size().width, i.image->size().height));
        }
 
-       auto merged = make_shared<Image>(images.front().image->pixel_format(), dcp::Size(all.width, all.height), true);
+       auto merged = make_shared<Image>(images.front().image->pixel_format(), dcp::Size(all.width, all.height), alignment);
        merged->make_transparent ();
        for (auto const& i: images) {
                merged->alpha_blend (i.image, i.position - all.position());
@@ -1158,7 +1195,7 @@ merge (list<PositionImage> images)
 bool
 operator== (Image const & a, Image const & b)
 {
-       if (a.planes() != b.planes() || a.pixel_format() != b.pixel_format() || a.aligned() != b.aligned()) {
+       if (a.planes() != b.planes() || a.pixel_format() != b.pixel_format() || a.alignment() != b.alignment()) {
                return false;
        }
 
@@ -1183,6 +1220,7 @@ operator== (Image const & a, Image const & b)
        return true;
 }
 
+
 /** Fade the image.
  *  @param f Amount to fade by; 0 is black, 1 is no fade.
  */
@@ -1303,16 +1341,18 @@ Image::fade (float f)
        }
 }
 
+
 shared_ptr<const Image>
-Image::ensure_aligned (shared_ptr<const Image> image)
+Image::ensure_alignment (shared_ptr<const Image> image, Image::Alignment alignment)
 {
-       if (image->aligned()) {
+       if (image->alignment() == alignment) {
                return image;
        }
 
-       return make_shared<Image>(image, true);
+       return make_shared<Image>(image, alignment);
 }
 
+
 size_t
 Image::memory_used () const
 {
@@ -1323,6 +1363,7 @@ Image::memory_used () const
        return m;
 }
 
+
 class Memory
 {
 public:
@@ -1340,6 +1381,7 @@ public:
        size_t size;
 };
 
+
 static void
 png_write_data (png_structp png_ptr, png_bytep data, png_size_t length)
 {
@@ -1360,31 +1402,35 @@ png_write_data (png_structp png_ptr, png_bytep data, png_size_t length)
        mem->size += length;
 }
 
+
 static void
 png_flush (png_structp)
 {
 
 }
 
+
 static void
 png_error_fn (png_structp png_ptr, char const * message)
 {
        reinterpret_cast<Image*>(png_get_error_ptr(png_ptr))->png_error (message);
 }
 
+
 void
 Image::png_error (char const * message)
 {
        throw EncodeError (String::compose ("Error during PNG write: %1", message));
 }
 
+
 dcp::ArrayData
 Image::as_png () const
 {
        DCPOMATIC_ASSERT (bytes_per_pixel(0) == 4);
        DCPOMATIC_ASSERT (planes() == 1);
        if (pixel_format() != AV_PIX_FMT_RGBA) {
-               return convert_pixel_format(dcp::YUVToRGB::REC709, AV_PIX_FMT_RGBA, true, false)->as_png();
+               return convert_pixel_format(dcp::YUVToRGB::REC709, AV_PIX_FMT_RGBA, Image::Alignment::PADDED, false)->as_png();
        }
 
        /* error handling? */
@@ -1433,13 +1479,29 @@ Image::video_range_to_full_range ()
                for (int y = 0; y < lines; ++y) {
                        uint8_t* q = p;
                        for (int x = 0; x < line_size()[0]; ++x) {
-                               *q = int((*q - 16) * factor);
+                               *q = clamp(lrintf((*q - 16) * factor), 0L, 255L);
                                ++q;
                        }
                        p += stride()[0];
                }
                break;
        }
+       case AV_PIX_FMT_RGB48LE:
+       {
+               float const factor = 65536.0 / 56064.0;
+               uint16_t* p = reinterpret_cast<uint16_t*>(data()[0]);
+               int const lines = sample_size(0).height;
+               for (int y = 0; y < lines; ++y) {
+                       uint16_t* q = p;
+                       int const line_size_pixels = line_size()[0] / 2;
+                       for (int x = 0; x < line_size_pixels; ++x) {
+                               *q = clamp(lrintf((*q - 4096) * factor), 0L, 65535L);
+                               ++q;
+                       }
+                       p += stride()[0] / 2;
+               }
+               break;
+       }
        case AV_PIX_FMT_GBRP12LE:
        {
                float const factor = 4096.0 / 3504.0;
@@ -1450,7 +1512,7 @@ Image::video_range_to_full_range ()
                                uint16_t* q = p;
                                int const line_size_pixels = line_size()[c] / 2;
                                for (int x = 0; x < line_size_pixels; ++x) {
-                                       *q = int((*q - 256) * factor);
+                                       *q = clamp(lrintf((*q - 256) * factor), 0L, 4095L);
                                        ++q;
                                }
                        }