#include "filter_graph.h"
#include "audio_buffers.h"
#include "ffmpeg_content.h"
+#include "image_proxy.h"
#include "i18n.h"
);
black->make_black ();
- video (image, false, _video_position);
+ video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
delta -= one_frame;
}
if (delta > -one_frame) {
/* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (image, false, _video_position);
+ video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
}
} else {
#include "image_content.h"
#include "image_decoder.h"
#include "image.h"
+#include "image_proxy.h"
#include "film.h"
#include "exceptions.h"
return;
}
- Magick::Image* magick_image = 0;
- boost::filesystem::path const path = _image_content->path (_image_content->still() ? 0 : _video_position);
- try {
- magick_image = new Magick::Image (path.string ());
- } catch (...) {
- throw OpenFileError (path);
- }
-
- libdcp::Size size (magick_image->columns(), magick_image->rows());
-
- _image.reset (new Image (PIX_FMT_RGB24, size, true));
-
- using namespace MagickCore;
-
- uint8_t* p = _image->data()[0];
- for (int y = 0; y < size.height; ++y) {
- uint8_t* q = p;
- for (int x = 0; x < size.width; ++x) {
- Magick::Color c = magick_image->pixelColor (x, y);
- *q++ = c.redQuantum() * 255 / QuantumRange;
- *q++ = c.greenQuantum() * 255 / QuantumRange;
- *q++ = c.blueQuantum() * 255 / QuantumRange;
- }
- p += _image->stride()[0];
- }
-
- delete magick_image;
-
+ _image.reset (new MagickImageProxy (_image_content->path (_image_content->still() ? 0 : _video_position)));
video (_image, false, _video_position);
}
private:
boost::shared_ptr<const ImageContent> _image_content;
- boost::shared_ptr<Image> _image;
+ boost::shared_ptr<ImageProxy> _image;
};
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <Magick++.h>
+#include <libdcp/util.h>
+#include <libdcp/raw_convert.h>
+#include "image_proxy.h"
+#include "image.h"
+#include "exceptions.h"
+#include "cross.h"
+
+#include "i18n.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+RawImageProxy::RawImageProxy (shared_ptr<Image> image)
+ : _image (image)
+{
+
+}
+
+RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket)
+{
+ libdcp::Size size (
+ xml->number_child<int> ("Width"), xml->number_child<int> ("Height")
+ );
+
+ _image.reset (new Image (PIX_FMT_RGB24, size, true));
+ _image->read_from_socket (socket);
+}
+
+shared_ptr<Image>
+RawImageProxy::image () const
+{
+ return _image;
+}
+
+void
+RawImageProxy::add_metadata (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text (N_("Raw"));
+ node->add_child("Width")->add_child_text (libdcp::raw_convert<string> (_image->size().width));
+ node->add_child("Height")->add_child_text (libdcp::raw_convert<string> (_image->size().height));
+}
+
+void
+RawImageProxy::send_binary (shared_ptr<Socket> socket) const
+{
+ _image->write_to_socket (socket);
+}
+
+MagickImageProxy::MagickImageProxy (boost::filesystem::path path)
+{
+ /* Read the file into a Blob */
+
+ boost::uintmax_t const size = boost::filesystem::file_size (path);
+ FILE* f = fopen_boost (path, "rb");
+ if (!f) {
+ throw OpenFileError (path);
+ }
+
+ uint8_t* data = new uint8_t[size];
+ if (fread (data, 1, size, f) != size) {
+ delete[] data;
+ throw ReadFileError (path);
+ }
+
+ fclose (f);
+ _blob.update (data, size);
+ delete[] data;
+}
+
+MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket)
+{
+ uint32_t const size = socket->read_uint32 ();
+ uint8_t* data = new uint8_t[size];
+ socket->read (data, size);
+ _blob.update (data, size);
+ delete[] data;
+}
+
+shared_ptr<Image>
+MagickImageProxy::image () const
+{
+ if (_image) {
+ return _image;
+ }
+
+ Magick::Image* magick_image = 0;
+ try {
+ magick_image = new Magick::Image (_blob);
+ } catch (...) {
+ throw DecodeError (_("Could not decode image file"));
+ }
+
+ libdcp::Size size (magick_image->columns(), magick_image->rows());
+
+ _image.reset (new Image (PIX_FMT_RGB24, size, true));
+
+ using namespace MagickCore;
+
+ uint8_t* p = _image->data()[0];
+ for (int y = 0; y < size.height; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < size.width; ++x) {
+ Magick::Color c = magick_image->pixelColor (x, y);
+ *q++ = c.redQuantum() * 255 / QuantumRange;
+ *q++ = c.greenQuantum() * 255 / QuantumRange;
+ *q++ = c.blueQuantum() * 255 / QuantumRange;
+ }
+ p += _image->stride()[0];
+ }
+
+ delete magick_image;
+
+ return _image;
+}
+
+void
+MagickImageProxy::add_metadata (xmlpp::Node* node) const
+{
+ node->add_child("Type")->add_child_text (N_("Magick"));
+}
+
+void
+MagickImageProxy::send_binary (shared_ptr<Socket> socket) const
+{
+ socket->write (_blob.length ());
+ socket->write ((uint8_t *) _blob.data (), _blob.length ());
+}
+
+shared_ptr<ImageProxy>
+image_proxy_factory (shared_ptr<cxml::Node> xml, shared_ptr<Socket> socket)
+{
+ if (xml->string_child("Type") == N_("Raw")) {
+ return shared_ptr<ImageProxy> (new RawImageProxy (xml, socket));
+ } else if (xml->string_child("Type") == N_("Magick")) {
+ return shared_ptr<MagickImageProxy> (new MagickImageProxy (xml, socket));
+ }
+
+ throw NetworkError (_("Unexpected image type received by server"));
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ This program 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 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/image_proxy.h
+ * @brief ImageProxy and subclasses.
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <boost/filesystem.hpp>
+#include <Magick++.h>
+#include <libxml++/libxml++.h>
+
+class Image;
+class Socket;
+
+namespace cxml {
+ class Node;
+}
+
+/** @class ImageProxy
+ * @brief A class which holds an Image, and can produce it on request.
+ *
+ * This is so that decoding of source images can be postponed until
+ * the encoder thread, where multi-threading is happening, instead
+ * of happening in a single-threaded decoder.
+ *
+ * For example, large TIFFs are slow to decode, so this class will keep
+ * the TIFF data TIFF until such a time that the actual image is needed.
+ * At this point, the class decodes the TIFF to an Image.
+ */
+class ImageProxy
+{
+public:
+ virtual boost::shared_ptr<Image> image () const = 0;
+ virtual void add_metadata (xmlpp::Node *) const = 0;
+ virtual void send_binary (boost::shared_ptr<Socket>) const = 0;
+};
+
+class RawImageProxy : public ImageProxy
+{
+public:
+ RawImageProxy (boost::shared_ptr<Image>);
+ RawImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket);
+
+ boost::shared_ptr<Image> image () const;
+ void add_metadata (xmlpp::Node *) const;
+ void send_binary (boost::shared_ptr<Socket>) const;
+
+private:
+ boost::shared_ptr<Image> _image;
+};
+
+class MagickImageProxy : public ImageProxy
+{
+public:
+ MagickImageProxy (boost::filesystem::path);
+ MagickImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket);
+
+ boost::shared_ptr<Image> image () const;
+ void add_metadata (xmlpp::Node *) const;
+ void send_binary (boost::shared_ptr<Socket>) const;
+
+private:
+ Magick::Blob _blob;
+ mutable boost::shared_ptr<Image> _image;
+};
+
+boost::shared_ptr<ImageProxy> image_proxy_factory (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket);
repeat_video.weak_piece,
repeat_video.image,
repeat_video.eyes,
+ repeat_video.part,
repeat_done > 0,
repeat_video.frame,
(repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
class Content;
class Decoder;
class Piece;
-class Image;
+class ImageProxy;
class Player;
struct IncomingVideo
{
public:
boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const Image> image;
+ boost::shared_ptr<const ImageProxy> image;
Eyes eyes;
+ Part part;
bool same;
VideoContent::Frame frame;
Time extra;
#include "playlist.h"
#include "job.h"
#include "image.h"
+#include "image_proxy.h"
#include "ratio.h"
#include "resampler.h"
#include "log.h"
/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const ImageProxy> image, Eyes eyes, Part part, bool same, VideoContent::Frame frame, Time extra)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
_last_incoming_video.image = image;
_last_incoming_video.eyes = eyes;
+ _last_incoming_video.part = part;
_last_incoming_video.same = same;
_last_incoming_video.frame = frame;
_last_incoming_video.extra = extra;
_video_container_size,
_film->scaler(),
eyes,
+ part,
content->colour_conversion()
)
);
if (fc) {
shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
+ fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
if (!reusing) {
shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
+ id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, _5, 0));
piece->decoder = id;
}
}
_black_frame.reset (
new PlayerVideoFrame (
- im,
+ shared_ptr<ImageProxy> (new RawImageProxy (im)),
Crop(),
_video_container_size,
_video_container_size,
Scaler::from_id ("bicubic"),
EYES_BOTH,
+ PART_WHOLE,
ColourConversion ()
)
);
_last_incoming_video.weak_piece,
_last_incoming_video.image,
_last_incoming_video.eyes,
+ _last_incoming_video.part,
_last_incoming_video.same,
_last_incoming_video.frame,
_last_incoming_video.extra
class Image;
class Resampler;
class PlayerVideoFrame;
+class ImageProxy;
/** @class Player
* @brief A class which can `play' a Playlist; emitting its audio and video.
friend class PlayerWrapper;
friend class Piece;
- void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
+ void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame, Time);
void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
void setup_pieces ();
#include <libdcp/raw_convert.h>
#include "player_video_frame.h"
#include "image.h"
+#include "image_proxy.h"
#include "scaler.h"
using std::string;
using libdcp::raw_convert;
PlayerVideoFrame::PlayerVideoFrame (
- shared_ptr<const Image> in,
+ shared_ptr<const ImageProxy> in,
Crop crop,
libdcp::Size inter_size,
libdcp::Size out_size,
Scaler const * scaler,
Eyes eyes,
+ Part part,
ColourConversion colour_conversion
)
: _in (in)
, _out_size (out_size)
, _scaler (scaler)
, _eyes (eyes)
+ , _part (part)
, _colour_conversion (colour_conversion)
{
_out_size = libdcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
_scaler = Scaler::from_id (node->string_child ("Scaler"));
_eyes = (Eyes) node->number_child<int> ("Eyes");
+ _part = (Part) node->number_child<int> ("Part");
_colour_conversion = ColourConversion (node);
- shared_ptr<Image> image (new Image (PIX_FMT_RGB24, libdcp::Size (node->number_child<int> ("InWidth"), node->number_child<int> ("InHeight")), true));
- image->read_from_socket (socket);
- _in = image;
+ _in = image_proxy_factory (node->node_child ("In"), socket);
if (node->optional_number_child<int> ("SubtitleX")) {
shared_ptr<Image>
PlayerVideoFrame::image () const
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
+ shared_ptr<Image> im = _in->image ();
+
+ Crop total_crop = _crop;
+ switch (_part) {
+ case PART_LEFT_HALF:
+ total_crop.right += im->size().width / 2;
+ break;
+ case PART_RIGHT_HALF:
+ total_crop.left += im->size().width / 2;
+ break;
+ case PART_TOP_HALF:
+ total_crop.bottom += im->size().height / 2;
+ break;
+ case PART_BOTTOM_HALF:
+ total_crop.top += im->size().height / 2;
+ break;
+ default:
+ break;
+ }
+
+ shared_ptr<Image> out = im->crop_scale_window (total_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
}
void
-PlayerVideoFrame::add_metadata (xmlpp::Element* node) const
+PlayerVideoFrame::add_metadata (xmlpp::Node* node) const
{
_crop.as_xml (node);
- node->add_child("InWidth")->add_child_text (raw_convert<string> (_in->size().width));
- node->add_child("InHeight")->add_child_text (raw_convert<string> (_in->size().height));
+ _in->add_metadata (node->add_child ("In"));
node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width));
node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height));
node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width));
node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height));
node->add_child("Scaler")->add_child_text (_scaler->id ());
node->add_child("Eyes")->add_child_text (raw_convert<string> (_eyes));
+ node->add_child("Part")->add_child_text (raw_convert<string> (_part));
_colour_conversion.as_xml (node);
if (_subtitle_image) {
node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle_image->size().width));
void
PlayerVideoFrame::send_binary (shared_ptr<Socket> socket) const
{
- _in->write_to_socket (socket);
+ _in->send_binary (socket);
if (_subtitle_image) {
_subtitle_image->write_to_socket (socket);
}
#include "colour_conversion.h"
class Image;
+class ImageProxy;
class Scaler;
class Socket;
class PlayerVideoFrame
{
public:
- PlayerVideoFrame (boost::shared_ptr<const Image>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, ColourConversion);
+ PlayerVideoFrame (boost::shared_ptr<const ImageProxy>, Crop, libdcp::Size, libdcp::Size, Scaler const *, Eyes, Part, ColourConversion);
PlayerVideoFrame (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>);
void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
boost::shared_ptr<Image> image () const;
- void add_metadata (xmlpp::Element* node) const;
+ void add_metadata (xmlpp::Node* node) const;
void send_binary (boost::shared_ptr<Socket> socket) const;
Eyes eyes () const {
}
private:
- boost::shared_ptr<const Image> _in;
+ boost::shared_ptr<const ImageProxy> _in;
Crop _crop;
libdcp::Size _inter_size;
libdcp::Size _out_size;
Scaler const * _scaler;
Eyes _eyes;
+ Part _part;
ColourConversion _colour_conversion;
boost::shared_ptr<const Image> _subtitle_image;
Position<int> _subtitle_position;
EYES_COUNT
};
+enum Part
+{
+ PART_LEFT_HALF,
+ PART_RIGHT_HALF,
+ PART_TOP_HALF,
+ PART_BOTTOM_HALF,
+ PART_WHOLE
+};
+
/** @struct Crop
* @brief A description of the crop of an image or video.
*/
}
void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const ImageProxy> image, bool same, VideoContent::Frame frame)
{
switch (_video_content->video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
- Video (image, EYES_BOTH, same, frame);
+ Video (image, EYES_BOTH, PART_WHOLE, same, frame);
break;
case VIDEO_FRAME_TYPE_3D_ALTERNATE:
- Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, same, frame / 2);
+ Video (image, (frame % 2) ? EYES_RIGHT : EYES_LEFT, PART_WHOLE, same, frame / 2);
break;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
- {
- int const half = image->size().width / 2;
- Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+ Video (image, EYES_LEFT, PART_LEFT_HALF, same, frame);
+ Video (image, EYES_RIGHT, PART_RIGHT_HALF, same, frame);
break;
- }
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
- {
- int const half = image->size().height / 2;
- Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+ Video (image, EYES_LEFT, PART_TOP_HALF, same, frame);
+ Video (image, EYES_RIGHT, PART_BOTTOM_HALF, same, frame);
break;
- }
case VIDEO_FRAME_TYPE_3D_LEFT:
- Video (image, EYES_LEFT, same, frame);
+ Video (image, EYES_LEFT, PART_WHOLE, same, frame);
break;
case VIDEO_FRAME_TYPE_3D_RIGHT:
- Video (image, EYES_RIGHT, same, frame);
+ Video (image, EYES_RIGHT, PART_WHOLE, same, frame);
break;
}
#include "util.h"
class VideoContent;
-class Image;
+class ImageProxy;
class VideoDecoder : public virtual Decoder
{
/** Emitted when a video frame is ready.
* First parameter is the video image.
* Second parameter is the eye(s) which should see this image.
- * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
+ * Third parameter is the part of this image that should be used.
+ * Fourth parameter is true if the image is the same as the last one that was emitted for this Eyes value.
* Fourth parameter is the frame within our source.
*/
- boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
+ boost::signals2::signal<void (boost::shared_ptr<const ImageProxy>, Eyes, Part, bool, VideoContent::Frame)> Video;
protected:
- void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void video (boost::shared_ptr<const ImageProxy>, bool, VideoContent::Frame);
boost::shared_ptr<const VideoContent> _video_content;
/** This is in frames without taking 3D into account (e.g. if we are doing 3D alternate,
* this would equal 2 on the left-eye second frame (not 1)).
image_content.cc
image_decoder.cc
image_examiner.cc
+ image_proxy.cc
job.cc
job_manager.cc
kdm.cc
#include "lib/dcp_video_frame.h"
#include "lib/scaler.h"
#include "lib/player_video_frame.h"
+#include "lib/image_proxy.h"
using std::list;
using boost::shared_ptr;
shared_ptr<PlayerVideoFrame> pvf (
new PlayerVideoFrame (
- image,
+ shared_ptr<ImageProxy> (new RawImageProxy (image)),
Crop (),
libdcp::Size (1998, 1080),
libdcp::Size (1998, 1080),
Scaler::from_id ("bicubic"),
EYES_BOTH,
+ PART_WHOLE,
ColourConversion ()
)
);