/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
+
/** @file src/image.cc
* @brief A class to describe a video image.
*/
-#include "image.h"
+
+#include "compose.hpp"
+#include "dcpomatic_socket.h"
#include "exceptions.h"
-#include "timer.h"
+#include "image.h"
#include "rect.h"
+#include "timer.h"
#include "util.h"
-#include "compose.hpp"
-#include "dcpomatic_socket.h"
#include <dcp/rgb_xyz.h>
#include <dcp/transfer_function.h>
extern "C" {
-#include <libswscale/swscale.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/pixdesc.h>
#include <libavutil/frame.h>
+#include <libavutil/pixdesc.h>
+#include <libavutil/pixfmt.h>
+#include <libswscale/swscale.h>
}
#include <png.h>
#if HAVE_VALGRIND_MEMCHECK_H
#endif
#include <iostream>
+
#include "i18n.h"
-using std::string;
-using std::min;
-using std::max;
-using std::cout;
+
using std::cerr;
+using std::cout;
using std::list;
+using std::make_shared;
+using std::max;
+using std::min;
using std::runtime_error;
-using boost::shared_ptr;
+using std::shared_ptr;
+using std::string;
using dcp::Size;
return 1;
}
- AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ auto d = av_pix_fmt_desc_get(_pixel_format);
if (!d) {
throw PixelFormatError ("line_factor()", _pixel_format);
}
return 1;
}
- AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ auto d = av_pix_fmt_desc_get(_pixel_format);
if (!d) {
throw PixelFormatError ("sample_size()", _pixel_format);
}
Image::sample_size (int n) const
{
return dcp::Size (
- lrint (ceil (static_cast<double>(size().width) / horizontal_factor (n))),
- lrint (ceil (static_cast<double>(size().height) / vertical_factor (n)))
+ lrint (ceil(static_cast<double>(size().width) / horizontal_factor(n))),
+ lrint (ceil(static_cast<double>(size().height) / vertical_factor(n)))
);
}
int
Image::planes () const
{
- AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ auto d = av_pix_fmt_desc_get(_pixel_format);
if (!d) {
throw PixelFormatError ("planes()", _pixel_format);
}
return d->nb_components;
}
+
+static
+int
+round_width_for_subsampling (int p, AVPixFmtDescriptor const * desc)
+{
+ return p & ~ ((1 << desc->log2_chroma_w) - 1);
+}
+
+
+static
+int
+round_height_for_subsampling (int p, AVPixFmtDescriptor const * desc)
+{
+ return p & ~ ((1 << desc->log2_chroma_h) - 1);
+}
+
+
/** Crop this image, scale it to `inter_size' and then place it in a black frame of `out_size'.
* @param crop Amount to crop by.
* @param inter_size Size to scale the cropped image to.
DCPOMATIC_ASSERT (out_size.width >= inter_size.width);
DCPOMATIC_ASSERT (out_size.height >= inter_size.height);
- shared_ptr<Image> out (new Image(out_format, out_size, out_aligned));
+ auto out = make_shared<Image>(out_format, out_size, out_aligned);
out->make_black ();
+ auto in_desc = av_pix_fmt_desc_get (_pixel_format);
+ if (!in_desc) {
+ throw PixelFormatError ("crop_scale_window()", _pixel_format);
+ }
+
+ /* Round down so that we crop only the number of pixels that is straightforward
+ * considering any subsampling.
+ */
+ Crop corrected_crop(
+ round_width_for_subsampling(crop.left, in_desc),
+ round_width_for_subsampling(crop.right, in_desc),
+ round_height_for_subsampling(crop.top, in_desc),
+ round_height_for_subsampling(crop.bottom, in_desc)
+ );
+
+ /* Also check that we aren't cropping more image than there actually is */
+ if ((corrected_crop.left + corrected_crop.right) >= (size().width - 4)) {
+ corrected_crop.left = 0;
+ corrected_crop.right = size().width - 4;
+ }
+
+ if ((corrected_crop.top + corrected_crop.bottom) >= (size().height - 4)) {
+ corrected_crop.top = 0;
+ corrected_crop.bottom = size().height - 4;
+ }
+
/* Size of the image after any crop */
- dcp::Size const cropped_size = crop.apply (size ());
+ auto const cropped_size = corrected_crop.apply (size());
/* Scale context for a scale from cropped_size to inter_size */
- struct SwsContext* scale_context = sws_getContext (
+ auto scale_context = sws_getContext (
cropped_size.width, cropped_size.height, pixel_format(),
inter_size.width, inter_size.height, out_format,
fast ? SWS_FAST_BILINEAR : SWS_BICUBIC, 0, 0, 0
throw runtime_error (N_("Could not allocate SwsContext"));
}
- DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUV_TO_RGB_COUNT);
- int const lut[dcp::YUV_TO_RGB_COUNT] = {
+ DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUVToRGB::COUNT);
+ int const lut[static_cast<int>(dcp::YUVToRGB::COUNT)] = {
SWS_CS_ITU601,
SWS_CS_ITU709
};
*/
sws_setColorspaceDetails (
scale_context,
- sws_getCoefficients (lut[yuv_to_rgb]), video_range == VIDEO_RANGE_VIDEO ? 0 : 1,
- sws_getCoefficients (lut[yuv_to_rgb]), out_video_range == VIDEO_RANGE_VIDEO ? 0 : 1,
+ sws_getCoefficients (lut[static_cast<int>(yuv_to_rgb)]), video_range == VideoRange::VIDEO ? 0 : 1,
+ sws_getCoefficients (lut[static_cast<int>(yuv_to_rgb)]), out_video_range == VideoRange::VIDEO ? 0 : 1,
0, 1 << 16, 1 << 16
);
- AVPixFmtDescriptor const * in_desc = av_pix_fmt_desc_get (_pixel_format);
- if (!in_desc) {
- throw PixelFormatError ("crop_scale_window()", _pixel_format);
- }
-
/* Prepare input data pointers with crop */
uint8_t* scale_in_data[planes()];
for (int c = 0; c < planes(); ++c) {
- /* To work out the crop in bytes, start by multiplying
- the crop by the (average) bytes per pixel. Then
- round down so that we don't crop a subsampled pixel until
- we've cropped all of its Y-channel pixels.
- */
- int const x = lrintf (bytes_per_pixel(c) * crop.left) & ~ ((int) in_desc->log2_chroma_w);
- scale_in_data[c] = data()[c] + x + stride()[c] * (crop.top / vertical_factor(c));
+ int const x = lrintf(bytes_per_pixel(c) * corrected_crop.left);
+ scale_in_data[c] = data()[c] + x + stride()[c] * (corrected_crop.top / vertical_factor(c));
}
- /* Corner of the image within out_size */
- Position<int> const corner ((out_size.width - inter_size.width) / 2, (out_size.height - inter_size.height) / 2);
-
- AVPixFmtDescriptor const * out_desc = av_pix_fmt_desc_get (out_format);
+ auto out_desc = av_pix_fmt_desc_get (out_format);
if (!out_desc) {
throw PixelFormatError ("crop_scale_window()", out_format);
}
+ /* Corner of the image within out_size */
+ Position<int> const corner (
+ round_width_for_subsampling((out_size.width - inter_size.width) / 2, out_desc),
+ round_height_for_subsampling((out_size.height - inter_size.height) / 2, out_desc)
+ );
+
uint8_t* scale_out_data[out->planes()];
for (int c = 0; c < out->planes(); ++c) {
- /* See the note in the crop loop above */
- int const x = lrintf (out->bytes_per_pixel(c) * corner.x) & ~ ((int) out_desc->log2_chroma_w);
+ int const x = lrintf(out->bytes_per_pixel(c) * corner.x);
scale_out_data[c] = out->data()[c] + x + out->stride()[c] * (corner.y / out->vertical_factor(c));
}
sws_freeContext (scale_context);
- if (crop != Crop() && cropped_size == inter_size && _pixel_format == out_format) {
+ if (corrected_crop != Crop() && cropped_size == inter_size) {
/* We are cropping without any scaling or pixel format conversion, so FFmpeg may have left some
data behind in our image. Clear it out. It may get to the point where we should just stop
trying to be clever with cropping.
*/
DCPOMATIC_ASSERT (aligned ());
- shared_ptr<Image> scaled (new Image (out_format, out_size, out_aligned));
-
- struct SwsContext* scale_context = sws_getContext (
+ auto scaled = make_shared<Image>(out_format, out_size, out_aligned);
+ auto scale_context = sws_getContext (
size().width, size().height, pixel_format(),
out_size.width, out_size.height, out_format,
(fast ? SWS_FAST_BILINEAR : SWS_BICUBIC) | SWS_ACCURATE_RND, 0, 0, 0
);
- DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUV_TO_RGB_COUNT);
- int const lut[dcp::YUV_TO_RGB_COUNT] = {
+ DCPOMATIC_ASSERT (yuv_to_rgb < dcp::YUVToRGB::COUNT);
+ int const lut[static_cast<int>(dcp::YUVToRGB::COUNT)] = {
SWS_CS_ITU601,
SWS_CS_ITU709
};
*/
sws_setColorspaceDetails (
scale_context,
- sws_getCoefficients (lut[yuv_to_rgb]), 0,
- sws_getCoefficients (lut[yuv_to_rgb]), 0,
+ sws_getCoefficients (lut[static_cast<int>(yuv_to_rgb)]), 0,
+ sws_getCoefficients (lut[static_cast<int>(yuv_to_rgb)]), 0,
0, 1 << 16, 1 << 16
);
{
memset (data()[0], 0, sample_size(0).height * stride()[0]);
for (int i = 1; i < 3; ++i) {
- int16_t* p = reinterpret_cast<int16_t*> (data()[i]);
+ auto p = reinterpret_cast<int16_t*> (data()[i]);
int const lines = sample_size(i).height;
for (int y = 0; y < lines; ++y) {
/* We divide by 2 here because we are writing 2 bytes at a time */
}
case AV_PIX_FMT_XYZ12LE:
{
- dcp::ColourConversion conv = dcp::ColourConversion::srgb_to_xyz();
+ auto conv = dcp::ColourConversion::srgb_to_xyz();
double fast_matrix[9];
dcp::combined_rgb_to_xyz (conv, fast_matrix);
double const * lut_in = conv.in()->lut (8, false);
}
case AV_PIX_FMT_YUV420P:
{
- shared_ptr<Image> yuv = other->convert_pixel_format (dcp::YUV_TO_RGB_REC709, _pixel_format, false, false);
+ auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, 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) {
}
case AV_PIX_FMT_YUV420P10:
{
- shared_ptr<Image> yuv = other->convert_pixel_format (dcp::YUV_TO_RGB_REC709, _pixel_format, false, false);
+ auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, 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) {
}
case AV_PIX_FMT_YUV422P10LE:
{
- shared_ptr<Image> yuv = other->convert_pixel_format (dcp::YUV_TO_RGB_REC709, _pixel_format, false, false);
+ auto yuv = other->convert_pixel_format (dcp::YUVToRGB::REC709, _pixel_format, false, 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) {
float
Image::bytes_per_pixel (int c) const
{
- AVPixFmtDescriptor const * d = av_pix_fmt_desc_get(_pixel_format);
+ auto d = av_pix_fmt_desc_get(_pixel_format);
if (!d) {
throw PixelFormatError ("bytes_per_pixel()", _pixel_format);
}
}
Image::Image (Image const & other)
- : boost::enable_shared_from_this<Image>(other)
+ : std::enable_shared_from_this<Image>(other)
, _size (other._size)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
Image::Image (AVFrame* frame)
: _size (frame->width, frame->height)
- , _pixel_format (static_cast<AVPixelFormat> (frame->format))
+ , _pixel_format (static_cast<AVPixelFormat>(frame->format))
, _aligned (true)
{
+ DCPOMATIC_ASSERT (_pixel_format != AV_PIX_FMT_NONE);
+
allocate ();
for (int i = 0; i < planes(); ++i) {
return _aligned;
}
+
PositionImage
merge (list<PositionImage> images)
{
if (images.empty ()) {
- return PositionImage ();
+ return {};
}
if (images.size() == 1) {
}
dcpomatic::Rect<int> all (images.front().position, images.front().image->size().width, images.front().image->size().height);
- for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
- all.extend (dcpomatic::Rect<int> (i->position, i->image->size().width, i->image->size().height));
+ for (auto const& i: images) {
+ all.extend (dcpomatic::Rect<int>(i.position, i.image->size().width, i.image->size().height));
}
- shared_ptr<Image> merged (new 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), true);
merged->make_transparent ();
- for (list<PositionImage>::const_iterator i = images.begin(); i != images.end(); ++i) {
- merged->alpha_blend (i->image, i->position - all.position());
+ for (auto const& i: images) {
+ merged->alpha_blend (i.image, i.position - all.position());
}
return PositionImage (merged, all.position ());
}
+
bool
operator== (Image const & a, Image const & b)
{
return image;
}
- return shared_ptr<Image> (new Image (image, true));
+ return make_shared<Image>(image, true);
}
size_t
static void
png_write_data (png_structp png_ptr, png_bytep data, png_size_t length)
{
- Memory* mem = reinterpret_cast<Memory*>(png_get_io_ptr(png_ptr));
+ auto mem = reinterpret_cast<Memory*>(png_get_io_ptr(png_ptr));
size_t size = mem->size + length;
if (mem->data) {
DCPOMATIC_ASSERT (bytes_per_pixel(0) == 4);
DCPOMATIC_ASSERT (planes() == 1);
if (pixel_format() != AV_PIX_FMT_RGBA) {
- return convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGBA, true, false)->as_png();
+ return convert_pixel_format(dcp::YUVToRGB::REC709, AV_PIX_FMT_RGBA, true, false)->as_png();
}
/* error handling? */