#include "timer.h"
#include "rect.h"
#include "util.h"
+#include "compose.hpp"
#include "dcpomatic_socket.h"
#include <dcp/rgb_xyz.h>
#include <dcp/transfer_function.h>
#include <libavutil/pixdesc.h>
#include <libavutil/frame.h>
}
+#include <png.h>
+#if HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
#include <iostream>
#include "i18n.h"
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;
}
SWS_CS_ITU709
};
+ /* The 3rd parameter here is:
+ 0 -> source range MPEG (i.e. "video", 16-235)
+ 1 -> source range JPEG (i.e. "full", 0-255)
+ And the 5th:
+ 0 -> destination range MPEG (i.e. "video", 16-235)
+ 1 -> destination range JPEG (i.e. "full", 0-255)
+
+ But remember: sws_setColorspaceDetails ignores
+ these parameters unless the image isYUV or isGray
+ (if it's neither, it uses video range for source
+ and destination).
+ */
sws_setColorspaceDetails (
scale_context,
sws_getCoefficients (lut[yuv_to_rgb]), 0,
0, 1 << 16, 1 << 16
);
- AVPixFmtDescriptor const * desc = av_pix_fmt_desc_get (_pixel_format);
- if (!desc) {
+ AVPixFmtDescriptor const * in_desc = av_pix_fmt_desc_get (_pixel_format);
+ if (!in_desc) {
throw PixelFormatError ("crop_scale_window()", _pixel_format);
}
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) desc->log2_chroma_w);
+ 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));
}
/* 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);
+ if (!out_desc) {
+ throw PixelFormatError ("crop_scale_window()", out_format);
+ }
+
uint8_t* scale_out_data[out->planes()];
for (int c = 0; c < out->planes(); ++c) {
- scale_out_data[c] = out->data()[c] + lrintf (out->bytes_per_pixel(c) * corner.x) + out->stride()[c] * (corner.y / out->vertical_factor(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);
+ scale_out_data[c] = out->data()[c] + x + out->stride()[c] * (corner.y / out->vertical_factor(c));
}
sws_scale (
SWS_CS_ITU709
};
+ /* The 3rd parameter here is:
+ 0 -> source range MPEG (i.e. "video", 16-235)
+ 1 -> source range JPEG (i.e. "full", 0-255)
+ And the 5th:
+ 0 -> destination range MPEG (i.e. "video", 16-235)
+ 1 -> destination range JPEG (i.e. "full", 0-255)
+
+ But remember: sws_setColorspaceDetails ignores
+ these parameters unless the image isYUV or isGray
+ (if it's neither, it uses video range for source
+ and destination).
+ */
sws_setColorspaceDetails (
scale_context,
sws_getCoefficients (lut[yuv_to_rgb]), 0,
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* We're blending BGRA images; first byte is blue, second byte is green, third byte red, fourth byte alpha */
- DCPOMATIC_ASSERT (other->pixel_format() == AV_PIX_FMT_BGRA);
+ /* We're blending RGBA or BGRA images */
+ DCPOMATIC_ASSERT (other->pixel_format() == AV_PIX_FMT_BGRA || other->pixel_format() == AV_PIX_FMT_RGBA);
+ int const blue = other->pixel_format() == AV_PIX_FMT_BGRA ? 0 : 2;
+ int const red = other->pixel_format() == AV_PIX_FMT_BGRA ? 2 : 0;
+
int const other_bpp = 4;
int start_tx = position.x;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
- tp[0] = op[2] * alpha + tp[0] * (1 - alpha);
+ tp[0] = op[red] * alpha + tp[0] * (1 - alpha);
tp[1] = op[1] * alpha + tp[1] * (1 - alpha);
- tp[2] = op[0] * alpha + tp[2] * (1 - alpha);
+ tp[2] = op[blue] * alpha + tp[2] * (1 - alpha);
tp += this_bpp;
op += other_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
- tp[0] = op[0] * alpha + tp[0] * (1 - alpha);
+ tp[0] = op[blue] * alpha + tp[0] * (1 - alpha);
tp[1] = op[1] * alpha + tp[1] * (1 - alpha);
- tp[2] = op[2] * alpha + tp[2] * (1 - alpha);
+ tp[2] = op[red] * alpha + tp[2] * (1 - alpha);
tp[3] = op[3] * alpha + tp[3] * (1 - alpha);
tp += this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
- tp[0] = op[2] * alpha + tp[0] * (1 - alpha);
+ tp[0] = op[red] * alpha + tp[0] * (1 - alpha);
tp[1] = op[1] * alpha + tp[1] * (1 - alpha);
- tp[2] = op[0] * alpha + tp[2] * (1 - alpha);
+ tp[2] = op[blue] * alpha + tp[2] * (1 - alpha);
tp[3] = op[3] * alpha + tp[3] * (1 - alpha);
tp += this_bpp;
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
/* Blend high bytes */
- tp[1] = op[2] * alpha + tp[1] * (1 - alpha);
+ tp[1] = op[red] * alpha + tp[1] * (1 - alpha);
tp[3] = op[1] * alpha + tp[3] * (1 - alpha);
- tp[5] = op[0] * alpha + tp[5] * (1 - alpha);
+ tp[5] = op[blue] * alpha + tp[5] * (1 - alpha);
tp += this_bpp;
op += other_bpp;
float const alpha = float (op[3]) / 255;
/* Convert sRGB to XYZ; op is BGRA. First, input gamma LUT */
- double const r = lut_in[op[2]];
+ double const r = lut_in[op[red]];
double const g = lut_in[op[1]];
- double const b = lut_in[op[0]];
+ double const b = lut_in[op[blue]];
/* RGB to XYZ, including Bradford transform and DCI companding */
double const x = max (0.0, min (65535.0, r * fast_matrix[0] + g * fast_matrix[1] + b * fast_matrix[2]));
testing suggests that it works.
*/
_data[i] = (uint8_t *) wrapped_av_malloc (_stride[i] * sample_size(i).height + _extra_pixels * bytes_per_pixel(i) + 32);
+#if HAVE_VALGRIND_MEMCHECK_H
+ /* The data between the end of the line size and the stride is undefined but processed by
+ libswscale, causing lots of valgrind errors. Mark it all defined to quell these errors.
+ */
+ VALGRIND_MAKE_MEM_DEFINED (_data[i], _stride[i] * sample_size(i).height + _extra_pixels * bytes_per_pixel(i) + 32);
+#endif
}
}
Image::Image (Image const & other)
- : _size (other._size)
+ : boost::enable_shared_from_this<Image>(other)
+ , _size (other._size)
, _pixel_format (other._pixel_format)
, _aligned (other._aligned)
, _extra_pixels (other._extra_pixels)
}
}
-shared_ptr<Image>
-Image::ensure_aligned (shared_ptr<Image> image)
+shared_ptr<const Image>
+Image::ensure_aligned (shared_ptr<const Image> image)
{
if (image->aligned()) {
return image;
}
return m;
}
+
+class Memory
+{
+public:
+ Memory ()
+ : data(0)
+ , size(0)
+ {}
+
+ ~Memory ()
+ {
+ free (data);
+ }
+
+ uint8_t* data;
+ size_t size;
+};
+
+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));
+ size_t size = mem->size + length;
+
+ if (mem->data) {
+ mem->data = reinterpret_cast<uint8_t*>(realloc(mem->data, size));
+ } else {
+ mem->data = reinterpret_cast<uint8_t*>(malloc(size));
+ }
+
+ if (!mem->data) {
+ throw EncodeError (N_("could not allocate memory for PNG"));
+ }
+
+ memcpy (mem->data + mem->size, data, 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::Data
+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::YUV_TO_RGB_REC709, AV_PIX_FMT_RGBA, true, false)->as_png();
+ }
+
+ /* error handling? */
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, reinterpret_cast<void*>(const_cast<Image*>(this)), png_error_fn, 0);
+ if (!png_ptr) {
+ throw EncodeError (N_("could not create PNG write struct"));
+ }
+
+ Memory state;
+
+ png_set_write_fn (png_ptr, &state, png_write_data, png_flush);
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+ throw EncodeError (N_("could not create PNG info struct"));
+ }
+
+ png_set_IHDR (png_ptr, info_ptr, size().width, size().height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ png_byte ** row_pointers = reinterpret_cast<png_byte **>(png_malloc(png_ptr, size().height * sizeof(png_byte *)));
+ for (int i = 0; i < size().height; ++i) {
+ row_pointers[i] = (png_byte *) (data()[0] + i * stride()[0]);
+ }
+
+ png_write_info (png_ptr, info_ptr);
+ png_write_image (png_ptr, row_pointers);
+ png_write_end (png_ptr, info_ptr);
+
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+ png_free (png_ptr, row_pointers);
+
+ return dcp::Data (state.data, state.size);
+}