diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 2c85812113..2bb6ab87a6 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -3145,7 +3145,7 @@ do_write_txo(ostream &out, const string &filename) const { //////////////////////////////////////////////////////////////////// // Function: Texture::do_unlock_and_reload_ram_image -// Access: Protected +// Access: Protected, Virtual // Description: This is similar to do_reload_ram_image(), except that // the lock is released during the actual operation, to // allow normal queries into the Texture object to @@ -3173,7 +3173,7 @@ do_unlock_and_reload_ram_image(bool allow_compression) { // pre-compressed, we don't consider it. has_ram_image = false; } - if (_loaded_from_image && !has_ram_image && !_fullpath.empty()) { + if (!has_ram_image && do_can_reload()) { nassertv(!_reloading); _reloading = true; @@ -4235,7 +4235,7 @@ do_has_uncompressed_ram_image() const { //////////////////////////////////////////////////////////////////// CPTA_uchar Texture:: do_get_ram_image() { - if (_loaded_from_image && !do_has_ram_image() && !_fullpath.empty()) { + if (!do_has_ram_image() && do_can_reload()) { do_unlock_and_reload_ram_image(true); // Normally, we don't update the _modified semaphores in a do_blah @@ -4273,7 +4273,7 @@ do_get_uncompressed_ram_image() { } // Couldn't uncompress the existing image. Try to reload it. - if (_loaded_from_image && (!do_has_ram_image() || _ram_image_compression != CM_off) && !_fullpath.empty()) { + if ((!do_has_ram_image() || _ram_image_compression != CM_off) && do_can_reload()) { do_unlock_and_reload_ram_image(false); } @@ -4691,6 +4691,20 @@ do_set_pad_size(int x, int y, int z) { _pad_z_size = z; } +//////////////////////////////////////////////////////////////////// +// Function: Texture::do_can_reload +// Access: Protected, Virtual +// Description: Returns true if we can safely call +// do_unlock_and_reload_ram_image() in order to make the +// image available, or false if we shouldn't do this +// (because we know from a priori knowledge that it +// wouldn't work anyway). +//////////////////////////////////////////////////////////////////// +bool Texture:: +do_can_reload() { + return (_loaded_from_image && !_fullpath.empty()); +} + //////////////////////////////////////////////////////////////////// // Function: Texture::do_reload // Access: Protected @@ -4698,7 +4712,7 @@ do_set_pad_size(int x, int y, int z) { //////////////////////////////////////////////////////////////////// bool Texture:: do_reload() { - if (_loaded_from_image && !_fullpath.empty()) { + if (do_can_reload()) { do_clear_ram_image(); do_unlock_and_reload_ram_image(true); if (do_has_ram_image()) { diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index a4256e3419..4b37466b69 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -496,7 +496,7 @@ protected: bool do_write_txo_file(const Filename &fullpath) const; bool do_write_txo(ostream &out, const string &filename) const; - void do_unlock_and_reload_ram_image(bool allow_compression); + virtual void do_unlock_and_reload_ram_image(bool allow_compression); virtual void do_reload_ram_image(bool allow_compression); PTA_uchar do_modify_ram_image(); PTA_uchar do_make_ram_image(); @@ -561,6 +561,7 @@ protected: void do_clear_ram_mipmap_images(); void do_generate_ram_mipmap_images(); void do_set_pad_size(int x, int y, int z); + virtual bool do_can_reload(); bool do_reload(); // This nested class declaration is used below. diff --git a/panda/src/gobj/videoTexture.I b/panda/src/gobj/videoTexture.I index 85fa2c29e2..6b97d45850 100644 --- a/panda/src/gobj/videoTexture.I +++ b/panda/src/gobj/videoTexture.I @@ -66,37 +66,6 @@ get_tex_scale() const { (float)_video_height / _y_size); } -//////////////////////////////////////////////////////////////////// -// Function: VideoTexture::set_video_size -// Access: Protected -// Description: Should be called by a derived class to set the size -// of the video when it is loaded. -//////////////////////////////////////////////////////////////////// -INLINE void VideoTexture:: -set_video_size(int video_width, int video_height) { - _video_width = video_width; - _video_height = video_height; -} - -//////////////////////////////////////////////////////////////////// -// Function: VideoTexture::consider_update -// Access: Protected -// Description: Calls update_frame() if the current frame has -// changed. -//////////////////////////////////////////////////////////////////// -INLINE void VideoTexture:: -consider_update() { - int this_frame = ClockObject::get_global_clock()->get_frame_count(); - if (this_frame != _last_frame_update) { - int frame = get_frame(); - if (_current_frame != frame) { - update_frame(frame); - _current_frame = frame; - } - _last_frame_update = this_frame; - } -} - //////////////////////////////////////////////////////////////////// // Function: VideoTexture::clear_current_frame // Access: Protected diff --git a/panda/src/gobj/videoTexture.cxx b/panda/src/gobj/videoTexture.cxx index f57f69989d..ca087a757d 100644 --- a/panda/src/gobj/videoTexture.cxx +++ b/panda/src/gobj/videoTexture.cxx @@ -107,6 +107,22 @@ cull_callback(CullTraverser *, const CullTraverserData &) const { return true; } +//////////////////////////////////////////////////////////////////// +// Function: VideoTexture::set_video_size +// Access: Protected +// Description: Should be called by a derived class to set the size +// of the video when it is loaded. Assumes the lock is +// held. +//////////////////////////////////////////////////////////////////// +void VideoTexture:: +set_video_size(int video_width, int video_height) { + _video_width = video_width; + _video_height = video_height; + do_set_pad_size(max(_x_size - _video_width, 0), + max(_y_size - _video_height, 0), + 0); +} + //////////////////////////////////////////////////////////////////// // Function: VideoTexture::do_has_ram_image // Access: Protected, Virtual @@ -134,6 +150,22 @@ reconsider_dirty() { consider_update(); } +//////////////////////////////////////////////////////////////////// +// Function: VideoTexture::do_unlock_and_reload_ram_image +// Access: Protected, Virtual +// Description: This is similar to do_reload_ram_image(), except that +// the lock is released during the actual operation, to +// allow normal queries into the Texture object to +// continue during what might be a slow operation. +// +// In the case of a VideoTexture, this is exactly the +// same as do_reload_ram_image(). +//////////////////////////////////////////////////////////////////// +void VideoTexture:: +do_unlock_and_reload_ram_image(bool) { + consider_update(); +} + //////////////////////////////////////////////////////////////////// // Function: VideoTexture::do_reload_ram_image // Access: Protected, Virtual @@ -143,7 +175,39 @@ reconsider_dirty() { // available, if possible. //////////////////////////////////////////////////////////////////// void VideoTexture:: -do_reload_ram_image() { +do_reload_ram_image(bool) { consider_update(); } +//////////////////////////////////////////////////////////////////// +// Function: VideoTexture::do_can_reload +// Access: Protected, Virtual +// Description: Returns true if we can safely call +// do_unlock_and_reload_ram_image() in order to make the +// image available, or false if we shouldn't do this +// (because we know from a priori knowledge that it +// wouldn't work anyway). +//////////////////////////////////////////////////////////////////// +bool VideoTexture:: +do_can_reload() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: VideoTexture::consider_update +// Access: Protected, Virtual +// Description: Calls update_frame() if the current frame has +// changed. +//////////////////////////////////////////////////////////////////// +void VideoTexture:: +consider_update() { + int this_frame = ClockObject::get_global_clock()->get_frame_count(); + if (this_frame != _last_frame_update) { + int frame = get_frame(); + if (_current_frame != frame) { + update_frame(frame); + _current_frame = frame; + } + _last_frame_update = this_frame; + } +} diff --git a/panda/src/gobj/videoTexture.h b/panda/src/gobj/videoTexture.h index 680d8bf943..591767a3f6 100644 --- a/panda/src/gobj/videoTexture.h +++ b/panda/src/gobj/videoTexture.h @@ -45,14 +45,16 @@ public: virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const; protected: - INLINE void set_video_size(int video_width, int video_height); + void set_video_size(int video_width, int video_height); virtual bool do_has_ram_image() const; virtual void reconsider_dirty(); - virtual void do_reload_ram_image(); + virtual void do_unlock_and_reload_ram_image(bool allow_compression); + virtual void do_reload_ram_image(bool allow_compression); + virtual bool do_can_reload(); - virtual INLINE void consider_update(); + virtual void consider_update(); INLINE void clear_current_frame(); virtual void update_frame(int frame)=0; diff --git a/panda/src/vision/openCVTexture.I b/panda/src/vision/openCVTexture.I index 57b61efa75..f744e01d3e 100644 --- a/panda/src/vision/openCVTexture.I +++ b/panda/src/vision/openCVTexture.I @@ -65,31 +65,3 @@ INLINE OpenCVTexture::VideoPage:: ~VideoPage() { } -//////////////////////////////////////////////////////////////////// -// Function: OpenCVTexture::consider_update -// Access: Protected -// Description: Calls update_frame() if the current frame has -// changed. -//////////////////////////////////////////////////////////////////// -INLINE void OpenCVTexture:: -consider_update() { - int this_frame = ClockObject::get_global_clock()->get_frame_count(); - if (this_frame != _last_frame_update) { - int frame = get_frame(); - if (_current_frame != frame) { - update_frame(frame); - _current_frame = frame; - } else { - // Loop through the pages to see if there's any camera stream to update. - int max_z = max(_z_size, (int)_pages.size()); - for (int z = 0; z < max_z; ++z) { - VideoPage &page = _pages[z]; - if (!page._color.is_from_file() || !page._alpha.is_from_file()) { - update_frame(frame, z); - } - } - } - _last_frame_update = this_frame; - } -} - diff --git a/panda/src/vision/openCVTexture.cxx b/panda/src/vision/openCVTexture.cxx index b0ae60705f..d301021383 100644 --- a/panda/src/vision/openCVTexture.cxx +++ b/panda/src/vision/openCVTexture.cxx @@ -58,6 +58,34 @@ OpenCVTexture:: ~OpenCVTexture() { } +//////////////////////////////////////////////////////////////////// +// Function: OpenCVTexture::consider_update +// Access: Protected, Virtual +// Description: Calls update_frame() if the current frame has +// changed. +//////////////////////////////////////////////////////////////////// +void OpenCVTexture:: +consider_update() { + int this_frame = ClockObject::get_global_clock()->get_frame_count(); + if (this_frame != _last_frame_update) { + int frame = get_frame(); + if (_current_frame != frame) { + update_frame(frame); + _current_frame = frame; + } else { + // Loop through the pages to see if there's any camera stream to update. + int max_z = max(_z_size, (int)_pages.size()); + for (int z = 0; z < max_z; ++z) { + VideoPage &page = _pages[z]; + if (!page._color.is_from_file() || !page._alpha.is_from_file()) { + update_frame(frame, z); + } + } + } + _last_frame_update = this_frame; + } +} + //////////////////////////////////////////////////////////////////// // Function: OpenCVTexture::do_make_copy // Access: Protected, Virtual @@ -97,23 +125,46 @@ do_assign(const OpenCVTexture ©) { // is specified) to accept its input from the camera // with the given index number, or the default camera if // the index number is -1 or unspecified. +// +// If alpha_file_channel is 0, then the camera image +// becomes a normal RGB texture. If it is 1, 2, or 3, +// then the camera image becomes an alpha texture, using +// the indicated channel of the source. //////////////////////////////////////////////////////////////////// bool OpenCVTexture:: -from_camera(int camera_index, int z, const LoaderOptions &options) { +from_camera(int camera_index, int z, int alpha_file_channel, + const LoaderOptions &options) { if (!do_reconsider_z_size(z)) { return false; } nassertr(z >= 0 && z < get_z_size(), false); - VideoPage &page = modify_page(z); - page._alpha.clear(); - if (!page._color.from_camera(camera_index)) { - return false; - } + _alpha_file_channel = alpha_file_channel; - if (!do_reconsider_video_properties(page._color, 3, z, options)) { + VideoPage &page = modify_page(z); + if (alpha_file_channel == 0) { + // A normal RGB texture. + page._alpha.clear(); + if (!page._color.from_camera(camera_index)) { + return false; + } + + if (!do_reconsider_video_properties(page._color, 3, z, options)) { + page._color.clear(); + return false; + } + } else { + // An alpha texture. page._color.clear(); - return false; + if (!page._alpha.from_camera(camera_index)) { + return false; + } + + if (!do_reconsider_video_properties(page._alpha, 1, z, options)) { + page._alpha.clear(); + return false; + } + do_set_format(F_alpha); } set_loaded_from_image(); @@ -161,7 +212,15 @@ do_reconsider_video_properties(const OpenCVTexture::VideoStream &stream, << "Loaded " << stream._filename << ", " << num_frames << " frames at " << frame_rate << " fps\n"; } + } else { + // In this case, we don't have a specific frame rate or number of + // frames. Let both values remain at 0. + if (vision_cat.is_debug()) { + vision_cat.debug() + << "Loaded camera stream\n"; + } } + int width = (int)cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FRAME_WIDTH); int height = (int)cvGetCaptureProperty(stream._capture, CV_CAP_PROP_FRAME_HEIGHT); @@ -238,74 +297,91 @@ update_frame(int frame) { //////////////////////////////////////////////////////////////////// void OpenCVTexture:: update_frame(int frame, int z) { - vision_cat.spam() << "Updating OpenCVTexture page " << z << "\n"; + if (vision_cat.is_spam()) { + vision_cat.spam() + << "Updating OpenCVTexture page " << z << "\n"; + } + VideoPage &page = _pages[z]; if (page._color.is_valid() || page._alpha.is_valid()) { do_modify_ram_image(); + ++_image_modified; } + ssize_t dest_x_pitch = _num_components * _component_width; + ssize_t dest_y_pitch = _x_size * dest_x_pitch; + if (page._color.is_valid()) { nassertv(get_num_components() >= 3 && get_component_width() == 1); - const unsigned char *source = page._color.get_frame_data(frame); - if (source != NULL) { + const unsigned char *r, *g, *b; + ssize_t x_pitch, y_pitch; + if (page._color.get_frame_data(frame, r, g, b, x_pitch, y_pitch)) { nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size); unsigned char *dest = _ram_images[0]._image.p() + do_get_expected_ram_page_size() * z; - int dest_row_width = (_x_size * _num_components * _component_width); - int source_row_width = get_video_width() * 3; - - if (get_num_components() == 3) { + if (_num_components == 3 && x_pitch == 3) { // The easy case--copy the whole thing in, row by row. + ssize_t copy_bytes = get_video_width() * dest_x_pitch; + nassertv(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch)); + for (int y = 0; y < get_video_height(); ++y) { - memcpy(dest, source, source_row_width); - dest += dest_row_width; - source += source_row_width; + memcpy(dest, r, copy_bytes); + dest += dest_y_pitch; + r += y_pitch; } } else { - // The harder case--interleave the color in with the alpha, - // pixel by pixel. - nassertv(get_num_components() == 4); + // The harder case--interleave in the color channels, pixel by + // pixel, possibly leaving room for alpha. + for (int y = 0; y < get_video_height(); ++y) { - int dx = 0; - int sx = 0; + ssize_t dx = 0; + ssize_t sx = 0; for (int x = 0; x < get_video_width(); ++x) { - dest[dx] = source[sx]; - dest[dx + 1] = source[sx + 1]; - dest[dx + 2] = source[sx + 2]; - dx += 4; - sx += 3; + dest[dx] = r[sx]; + dest[dx + 1] = g[sx]; + dest[dx + 2] = b[sx]; + dx += dest_x_pitch; + sx += x_pitch; } - dest += dest_row_width; - source += source_row_width; + dest += dest_y_pitch; + r += y_pitch; + g += y_pitch; + b += y_pitch; } } } } if (page._alpha.is_valid()) { - nassertv(get_num_components() == 4 && get_component_width() == 1); + nassertv(get_component_width() == 1); - const unsigned char *source = page._alpha.get_frame_data(frame); - if (source != NULL) { + const unsigned char *source[3]; + ssize_t x_pitch, y_pitch; + if (page._alpha.get_frame_data(frame, source[0], source[1], source[2], + x_pitch, y_pitch)) { nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size); unsigned char *dest = _ram_images[0]._image.p() + do_get_expected_ram_page_size() * z; - int dest_row_width = (_x_size * _num_components * _component_width); - int source_row_width = get_video_width() * 3; - // Interleave the alpha in with the color, pixel by pixel. // Even though the alpha will probably be a grayscale video, // the OpenCV library presents it as RGB. + const unsigned char *sch = source[0]; + if (_alpha_file_channel >= 1 && _alpha_file_channel <= 3) { + sch = source[_alpha_file_channel - 1]; + } + for (int y = 0; y < get_video_height(); ++y) { - int dx = 3; - int sx = _alpha_file_channel; + // Start dx at _num_components - 1, which writes to the last + // channel, i.e. the alpha channel. + ssize_t dx = (_num_components - 1) * _component_width; + ssize_t sx = 0; for (int x = 0; x < get_video_width(); ++x) { - dest[dx] = source[sx]; - dx += 4; - sx += 3; + dest[dx] = sch[sx]; + dx += dest_x_pitch; + sx += x_pitch; } - dest += dest_row_width; - source += source_row_width; + dest += dest_y_pitch; + sch += y_pitch; } } } @@ -478,14 +554,33 @@ OpenCVTexture::VideoStream:: //////////////////////////////////////////////////////////////////// // Function: OpenCVTexture::VideoStream::get_frame_data // Access: Public -// Description: Returns the pointer to the beginning of the +// Description: Gets the data needed to traverse through the // decompressed buffer for the indicated frame number. // It is most efficient to call this in increasing order -// of frame number. +// of frame number. Returns true on success, false on +// failure. +// +// In the case of a success indication (true return +// value), the three pointers r, g, b are loaded with +// the addresses of the three components of the +// bottom-left pixel of the image. (They will be +// adjacent in memory in the case of an interleaved +// image, and separated in the case of a +// separate-channel image.) The x_pitch value is filled +// with the amount to add to each pointer to advance to +// the pixel to the right; and the y_pitch value is +// filled with the amount to add to each pointer to +// advance to the pixel above. Note that these values +// may be negative (particularly in the case of a +// top-down image). //////////////////////////////////////////////////////////////////// -const unsigned char *OpenCVTexture::VideoStream:: -get_frame_data(int frame) { - nassertr(is_valid(), NULL); +bool OpenCVTexture::VideoStream:: +get_frame_data(int frame, + const unsigned char *&r, + const unsigned char *&g, + const unsigned char *&b, + ssize_t &x_pitch, ssize_t &y_pitch) { + nassertr(is_valid(), false); if (is_from_file() && _next_frame != frame) { cvSetCaptureProperty(_capture, CV_CAP_PROP_POS_FRAMES, frame); @@ -494,8 +589,34 @@ get_frame_data(int frame) { _next_frame = frame + 1; IplImage *image = cvQueryFrame(_capture); if (image == NULL) { - return NULL; + return false; } + + r = (const unsigned char *)image->imageData; + g = r + 1; + b = g + 1; + x_pitch = 3; + y_pitch = image->widthStep; + + if (image->dataOrder == 1) { + // Separate channel images. That means a block of r, followed by + // a block of g, followed by a block of b. + x_pitch = 1; + g = r + image->height * y_pitch; + b = g + image->height * y_pitch; + } + + if (image->origin == 0) { + // The image data starts with the top row and ends with the bottom + // row--the opposite of Texture::_ram_data's storage convention. + // Therefore, we must increment the initial pointers to the last + // row, and count backwards. + r += (image->height - 1) * y_pitch; + g += (image->height - 1) * y_pitch; + b += (image->height - 1) * y_pitch; + y_pitch = -y_pitch; + } + return (const unsigned char *)image->imageData; } diff --git a/panda/src/vision/openCVTexture.h b/panda/src/vision/openCVTexture.h index 4ef777ba7a..a8b1f6189a 100644 --- a/panda/src/vision/openCVTexture.h +++ b/panda/src/vision/openCVTexture.h @@ -40,13 +40,14 @@ PUBLISHED: virtual ~OpenCVTexture(); bool from_camera(int camera_index = -1, int z = 0, + int alpha_file_channel = 0, const LoaderOptions &options = LoaderOptions()); public: static PT(Texture) make_texture(); protected: - virtual INLINE void consider_update(); + virtual void consider_update(); virtual PT(Texture) do_make_copy(); void do_assign(const OpenCVTexture ©); @@ -81,7 +82,11 @@ private: void clear(); INLINE bool is_valid() const; INLINE bool is_from_file() const; - const unsigned char *get_frame_data(int frame); + bool get_frame_data(int frame, + const unsigned char *&r, + const unsigned char *&g, + const unsigned char *&b, + ssize_t &x_pitch, ssize_t &y_pitch); CvCapture *_capture; Filename _filename;