1 ///@file
2 /// An image on the Canvas
3 //
4 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Library General Public
8 // License as published by the Free Software Foundation; either
9 // version 2 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Library General Public License for more details.
15 //
16 // You should have received a copy of the GNU Library General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19
20 #include <simgear_config.h>
21
22 #include "CanvasImage.hxx"
23
24 #include <simgear/canvas/Canvas.hxx>
25 #include <simgear/canvas/CanvasMgr.hxx>
26 #include <simgear/canvas/CanvasSystemAdapter.hxx>
27 #include <simgear/canvas/events/KeyboardEvent.hxx>
28 #include <simgear/canvas/events/MouseEvent.hxx>
29 #include <simgear/scene/util/OsgMath.hxx>
30 #include <simgear/scene/util/parse_color.hxx>
31 #include <simgear/misc/sg_path.hxx>
32
33 #include <osg/Array>
34 #include <osg/Geometry>
35 #include <osg/PrimitiveSet>
36 #include <osgDB/Registry>
37 #include <osg/Version>
38
39 namespace simgear
40 {
41 namespace canvas
42 {
43 /**
44 * Callback to enable/disable rendering of canvas displayed inside windows or
45 * other canvases.
46 */
47 class CullCallback:
48 public osg::Drawable::CullCallback
49 {
50 public:
51 CullCallback(const CanvasWeakPtr& canvas);
52 void cullNextFrame();
53
54 private:
55 CanvasWeakPtr _canvas;
56 mutable bool _cull_next_frame;
57
58 virtual bool cull( osg::NodeVisitor* nv,
59 osg::Drawable* drawable,
60 osg::RenderInfo* renderInfo ) const;
61 };
62
63 //----------------------------------------------------------------------------
CullCallback(const CanvasWeakPtr & canvas)64 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
65 _canvas( canvas ),
66 _cull_next_frame( false )
67 {
68
69 }
70
71 //----------------------------------------------------------------------------
cullNextFrame()72 void CullCallback::cullNextFrame()
73 {
74 _cull_next_frame = true;
75 }
76
77 //----------------------------------------------------------------------------
cull(osg::NodeVisitor * nv,osg::Drawable * drawable,osg::RenderInfo * renderInfo) const78 bool CullCallback::cull( osg::NodeVisitor* nv,
79 osg::Drawable* drawable,
80 osg::RenderInfo* renderInfo ) const
81 {
82 CanvasPtr canvas = _canvas.lock();
83 if( canvas )
84 canvas->enableRendering();
85
86 if( !_cull_next_frame )
87 // TODO check if window/image should be culled
88 return false;
89
90 _cull_next_frame = false;
91 return true;
92 }
93
94 //----------------------------------------------------------------------------
95 const std::string Image::TYPE_NAME = "image";
96
97 //----------------------------------------------------------------------------
staticInit()98 void Image::staticInit()
99 {
100 if( isInit<Image>() )
101 return;
102
103 addStyle("fill", "color", &Image::setFill);
104 addStyle("outset", "", &Image::setOutset);
105 addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
106 addStyle("slice", "", &Image::setSlice);
107 addStyle("slice-width", "", &Image::setSliceWidth);
108
109 osgDB::Registry* reg = osgDB::Registry::instance();
110 if( !reg->getReaderWriterForExtension("png") )
111 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Missing 'png' image reader");
112 }
113
114 //----------------------------------------------------------------------------
Image(const CanvasWeakPtr & canvas,const SGPropertyNode_ptr & node,const Style & parent_style,ElementWeakPtr parent)115 Image::Image( const CanvasWeakPtr& canvas,
116 const SGPropertyNode_ptr& node,
117 const Style& parent_style,
118 ElementWeakPtr parent ):
119 Element(canvas, node, parent_style, parent),
120 _texture(new osg::Texture2D),
121 _node_src_rect( node->getNode("source", 0, true) )
122 {
123 staticInit();
124
125 _geom = new osg::Geometry;
126 _geom->setUseDisplayList(false);
127
128 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
129 stateSet->setTextureAttributeAndModes(0, _texture.get());
130 stateSet->setDataVariance(osg::Object::STATIC);
131
132 // allocate arrays for the image
133 _vertices = new osg::Vec3Array(4);
134 _vertices->setDataVariance(osg::Object::DYNAMIC);
135 _geom->setVertexArray(_vertices);
136
137 _texCoords = new osg::Vec2Array(4);
138 _texCoords->setDataVariance(osg::Object::DYNAMIC);
139 _geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
140
141 _colors = new osg::Vec4Array(1);
142 _colors->setDataVariance(osg::Object::DYNAMIC);
143 _geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
144
145 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
146 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
147 _prim->setDataVariance(osg::Object::DYNAMIC);
148 _geom->addPrimitiveSet(_prim);
149
150 setDrawable(_geom);
151
152 setFill("#ffffff"); // TODO how should we handle default values?
153 setupStyle();
154 }
155
156 //----------------------------------------------------------------------------
~Image()157 Image::~Image()
158 {
159 if( _http_request )
160 {
161 Canvas::getSystemAdapter()
162 ->getHTTPClient()
163 ->cancelRequest(_http_request, "image destroyed");
164 }
165 }
166
167 //----------------------------------------------------------------------------
valueChanged(SGPropertyNode * child)168 void Image::valueChanged(SGPropertyNode* child)
169 {
170 // If the image is switched from invisible to visible, and it shows a
171 // canvas, we need to delay showing it by one frame to ensure the canvas is
172 // updated before the image is displayed.
173 //
174 // As canvas::Element handles and filters changes to the "visible" property
175 // we can not check this in Image::childChanged but instead have to override
176 // Element::valueChanged.
177 if( !isVisible()
178 && child->getParent() == _node
179 && child->getNameString() == "visible"
180 && child->getBoolValue() )
181 {
182 CullCallback* cb =
183 #if OSG_VERSION_LESS_THAN(3,3,2)
184 static_cast<CullCallback*>
185 #else
186 dynamic_cast<CullCallback*>
187 #endif
188 ( _geom->getCullCallback() );
189
190 if( cb )
191 cb->cullNextFrame();
192 }
193
194 Element::valueChanged(child);
195 }
196
197 //----------------------------------------------------------------------------
setSrcCanvas(CanvasPtr canvas)198 void Image::setSrcCanvas(CanvasPtr canvas)
199 {
200 CanvasPtr src_canvas = _src_canvas.lock(),
201 self_canvas = _canvas.lock();
202
203 if( src_canvas )
204 src_canvas->removeParentCanvas(self_canvas);
205 if( self_canvas )
206 self_canvas->removeChildCanvas(src_canvas);
207
208 _src_canvas = src_canvas = canvas;
209 _attributes_dirty |= SRC_CANVAS;
210 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
211
212 if( src_canvas )
213 {
214 setupDefaultDimensions();
215
216 if( self_canvas )
217 {
218 self_canvas->addChildCanvas(src_canvas);
219 src_canvas->addParentCanvas(self_canvas);
220 }
221 }
222 }
223
224 //----------------------------------------------------------------------------
getSrcCanvas() const225 CanvasWeakPtr Image::getSrcCanvas() const
226 {
227 return _src_canvas;
228 }
229
230 //----------------------------------------------------------------------------
setImage(osg::ref_ptr<osg::Image> img)231 void Image::setImage(osg::ref_ptr<osg::Image> img)
232 {
233 // remove canvas...
234 setSrcCanvas( CanvasPtr() );
235
236 _texture->setResizeNonPowerOfTwoHint(false);
237 _texture->setImage(img);
238 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
239 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
240 _geom->getOrCreateStateSet()
241 ->setTextureAttributeAndModes(0, _texture);
242
243 if( img )
244 setupDefaultDimensions();
245 }
246
247 //----------------------------------------------------------------------------
setFill(const std::string & fill)248 void Image::setFill(const std::string& fill)
249 {
250 osg::Vec4 color(1,1,1,1);
251 if( !fill.empty() // If no color is given default to white
252 && !parseColor(fill, color) )
253 return;
254 setFill(color);
255 }
256
257 //----------------------------------------------------------------------------
setFill(const osg::Vec4 & color)258 void Image::setFill(const osg::Vec4& color)
259 {
260 _colors->front() = color;
261 _colors->dirty();
262 }
263
264
265 //----------------------------------------------------------------------------
setOutset(const std::string & outset)266 void Image::setOutset(const std::string& outset)
267 {
268 _outset = CSSBorder::parse(outset);
269 _attributes_dirty |= DEST_SIZE;
270 }
271
272 //----------------------------------------------------------------------------
setPreserveAspectRatio(const std::string & scale)273 void Image::setPreserveAspectRatio(const std::string& scale)
274 {
275 _preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
276 _attributes_dirty |= SRC_RECT;
277 }
278
279 //----------------------------------------------------------------------------
setSourceRect(const SGRect<float> & sourceRect)280 void Image::setSourceRect(const SGRect<float>& sourceRect)
281 {
282 _attributes_dirty |= SRC_RECT;
283 _src_rect = sourceRect;
284 }
285
286 //----------------------------------------------------------------------------
setSlice(const std::string & slice)287 void Image::setSlice(const std::string& slice)
288 {
289 _slice = CSSBorder::parse(slice);
290 _attributes_dirty |= SRC_RECT | DEST_SIZE;
291 }
292
293 //----------------------------------------------------------------------------
setSliceWidth(const std::string & width)294 void Image::setSliceWidth(const std::string& width)
295 {
296 _slice_width = CSSBorder::parse(width);
297 _attributes_dirty |= DEST_SIZE;
298 }
299
300 //----------------------------------------------------------------------------
getRegion() const301 const SGRect<float>& Image::getRegion() const
302 {
303 return _region;
304 }
305
306 //----------------------------------------------------------------------------
handleEvent(const EventPtr & event)307 bool Image::handleEvent(const EventPtr& event)
308 {
309 bool handled = Element::handleEvent(event);
310
311 CanvasPtr src_canvas = _src_canvas.lock();
312 if( !src_canvas )
313 return handled;
314
315 if( MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get()) )
316 {
317 mouse_event.reset( new MouseEvent(*mouse_event) );
318
319 mouse_event->client_pos = mouse_event->local_pos
320 - toOsg(_region.getMin());
321
322 osg::Vec2f size(_region.width(), _region.height());
323 if( _outset.isValid() )
324 {
325 CSSBorder::Offsets outset =
326 _outset.getAbsOffsets(getTextureDimensions());
327
328 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
329 size.x() += outset.l + outset.r;
330 size.y() += outset.t + outset.b;
331 }
332
333 // Scale event pos according to canvas view size vs. displayed/screen size
334 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
335 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
336 mouse_event->local_pos = mouse_event->client_pos;
337
338 handled |= src_canvas->handleMouseEvent(mouse_event);
339 }
340 else if( KeyboardEventPtr keyboard_event =
341 dynamic_cast<KeyboardEvent*>(event.get()) )
342 {
343 handled |= src_canvas->handleKeyboardEvent(keyboard_event);
344 }
345
346 return handled;
347 }
348
349 //----------------------------------------------------------------------------
updateImpl(double dt)350 void Image::updateImpl(double dt)
351 {
352 Element::updateImpl(dt);
353
354 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
355 (
356 _geom->getOrCreateStateSet()
357 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
358 );
359 auto canvas = _src_canvas.lock();
360
361 if( (_attributes_dirty & SRC_CANVAS)
362 // check if texture has changed (eg. due to resizing)
363 || (canvas && texture != canvas->getTexture()) )
364 {
365 _geom->getOrCreateStateSet()
366 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
367
368 if( !canvas || canvas->isInit() )
369 _attributes_dirty &= ~SRC_CANVAS;
370 }
371
372 if( !_attributes_dirty )
373 return;
374
375 const SGRect<int>& tex_dim = getTextureDimensions();
376
377 // http://www.w3.org/TR/css3-background/#border-image-slice
378
379 // The ‘fill’ keyword, if present, causes the middle part of the image to be
380 // preserved. (By default it is discarded, i.e., treated as empty.)
381 bool fill = (_slice.getKeyword() == "fill");
382
383 if( _attributes_dirty & DEST_SIZE )
384 {
385 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
386
387 if( num_vertices != _prim->getNumPrimitives() )
388 {
389 _vertices->resize(num_vertices);
390 _texCoords->resize(num_vertices);
391 _prim->setCount(num_vertices);
392 _prim->dirty();
393
394 _attributes_dirty |= SRC_RECT;
395 }
396
397 // http://www.w3.org/TR/css3-background/#border-image-outset
398 SGRect<float> region = _region;
399 if( _outset.isValid() )
400 {
401 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
402 region.t() -= outset.t;
403 region.r() += outset.r;
404 region.b() += outset.b;
405 region.l() -= outset.l;
406 }
407
408 if( !_slice.isValid() )
409 {
410 setQuad(0, region.getMin(), region.getMax());
411
412 if( !_preserve_aspect_ratio.scaleToFill() )
413 // We need to update texture coordinates to keep the aspect ratio
414 _attributes_dirty |= SRC_RECT;
415 }
416 else
417 {
418 /*
419 Image slice, 9-scale, whatever it is called. The four corner images
420 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
421 fill the remaining space up to the specified size.
422
423 x[0] x[1] x[2] x[3]
424 | | | |
425 -------------------- - y[0]
426 | tl | top | tr |
427 -------------------- - y[1]
428 | | | |
429 | l | | r |
430 | e | center | i |
431 | f | | g |
432 | t | | h |
433 | | | t |
434 -------------------- - y[2]
435 | bl | bottom | br |
436 -------------------- - y[3]
437 */
438
439 const CSSBorder::Offsets& slice =
440 (_slice_width.isValid() ? _slice_width : _slice)
441 .getAbsOffsets(tex_dim);
442 float x[4] = {
443 region.l(),
444 region.l() + slice.l,
445 region.r() - slice.r,
446 region.r()
447 };
448 float y[4] = {
449 region.t(),
450 region.t() + slice.t,
451 region.b() - slice.b,
452 region.b()
453 };
454
455 int i = 0;
456 for(int ix = 0; ix < 3; ++ix)
457 for(int iy = 0; iy < 3; ++iy)
458 {
459 if( ix == 1 && iy == 1 && !fill )
460 // The ‘fill’ keyword, if present, causes the middle part of the
461 // image to be filled.
462 continue;
463
464 setQuad( i++,
465 SGVec2f(x[ix ], y[iy ]),
466 SGVec2f(x[ix + 1], y[iy + 1]) );
467 }
468 }
469
470 _vertices->dirty();
471 _attributes_dirty &= ~DEST_SIZE;
472 _geom->dirtyBound();
473 }
474
475 if( _attributes_dirty & SRC_RECT )
476 {
477 SGRect<float> src_rect = _src_rect;
478 if( !_node_src_rect->getBoolValue("normalized", true) )
479 {
480 src_rect.t() /= tex_dim.height();
481 src_rect.r() /= tex_dim.width();
482 src_rect.b() /= tex_dim.height();
483 src_rect.l() /= tex_dim.width();
484 }
485
486 // Image coordinate systems y-axis is flipped
487 std::swap(src_rect.t(), src_rect.b());
488
489 if( !_slice.isValid() )
490 {
491 // Image scaling preserving aspect ratio. Change texture coordinates to
492 // scale image accordingly.
493 //
494 // TODO allow to specify what happens to not filled space (eg. color,
495 // or texture repeat/mirror)
496 //
497 // http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
498 if( !_preserve_aspect_ratio.scaleToFill() )
499 {
500 osg::BoundingBox const& bb = getBoundingBox();
501 float dst_width = bb._max.x() - bb._min.x(),
502 dst_height = bb._max.y() - bb._min.y();
503 float scale_x = dst_width / tex_dim.width(),
504 scale_y = dst_height / tex_dim.height();
505
506 float scale = _preserve_aspect_ratio.scaleToFit()
507 ? std::min(scale_x, scale_y)
508 : std::max(scale_x, scale_y);
509
510 if( scale_x != scale )
511 {
512 float d = scale_x / scale - 1;
513 if( _preserve_aspect_ratio.alignX()
514 == SVGpreserveAspectRatio::ALIGN_MIN )
515 {
516 src_rect.r() += d;
517 }
518 else if( _preserve_aspect_ratio.alignX()
519 == SVGpreserveAspectRatio::ALIGN_MAX )
520 {
521 src_rect.l() -= d;
522 }
523 else
524 {
525 src_rect.l() -= d / 2;
526 src_rect.r() += d / 2;
527 }
528 }
529
530 if( scale_y != scale )
531 {
532 float d = scale_y / scale - 1;
533 if( _preserve_aspect_ratio.alignY()
534 == SVGpreserveAspectRatio::ALIGN_MIN )
535 {
536 src_rect.b() -= d;
537 }
538 else if( _preserve_aspect_ratio.alignY()
539 == SVGpreserveAspectRatio::ALIGN_MAX )
540 {
541 src_rect.t() += d;
542 }
543 else
544 {
545 src_rect.t() += d / 2;
546 src_rect.b() -= d / 2;
547 }
548 }
549 }
550
551 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
552 }
553 else
554 {
555 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
556 float x[4] = {
557 src_rect.l(),
558 src_rect.l() + slice.l,
559 src_rect.r() - slice.r,
560 src_rect.r()
561 };
562 float y[4] = {
563 src_rect.t(),
564 src_rect.t() - slice.t,
565 src_rect.b() + slice.b,
566 src_rect.b()
567 };
568
569 int i = 0;
570 for(int ix = 0; ix < 3; ++ix)
571 for(int iy = 0; iy < 3; ++iy)
572 {
573 if( ix == 1 && iy == 1 && !fill )
574 // The ‘fill’ keyword, if present, causes the middle part of the
575 // image to be filled.
576 continue;
577
578 setQuadUV( i++,
579 SGVec2f(x[ix ], y[iy ]),
580 SGVec2f(x[ix + 1], y[iy + 1]) );
581 }
582 }
583
584 _texCoords->dirty();
585 _attributes_dirty &= ~SRC_RECT;
586 }
587 }
588
589 //----------------------------------------------------------------------------
childChanged(SGPropertyNode * child)590 void Image::childChanged(SGPropertyNode* child)
591 {
592 const std::string& name = child->getNameString();
593
594 if( child->getParent() == _node_src_rect )
595 {
596 _attributes_dirty |= SRC_RECT;
597
598 if( name == "left" )
599 _src_rect.setLeft( child->getFloatValue() );
600 else if( name == "right" )
601 _src_rect.setRight( child->getFloatValue() );
602 else if( name == "top" )
603 _src_rect.setTop( child->getFloatValue() );
604 else if( name == "bottom" )
605 _src_rect.setBottom( child->getFloatValue() );
606
607 return;
608 }
609 else if( child->getParent() != _node )
610 return;
611
612 if( name == "x" )
613 {
614 _region.setX( child->getFloatValue() );
615 _attributes_dirty |= DEST_SIZE;
616 }
617 else if( name == "y" )
618 {
619 _region.setY( child->getFloatValue() );
620 _attributes_dirty |= DEST_SIZE;
621 }
622 else if( name == "size" )
623 {
624 if( child->getIndex() == 0 )
625 _region.setWidth( child->getFloatValue() );
626 else
627 _region.setHeight( child->getFloatValue() );
628
629 _attributes_dirty |= DEST_SIZE;
630 }
631 else if( name == "src" || name == "file" )
632 {
633 if( name == "file" )
634 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
635
636 // Abort pending request
637 if( _http_request )
638 {
639 Canvas::getSystemAdapter()
640 ->getHTTPClient()
641 ->cancelRequest(_http_request, "setting new image");
642 _http_request.reset();
643 }
644
645 static const std::string PROTOCOL_SEP = "://";
646
647 std::string url = child->getStringValue(),
648 protocol, path;
649
650 size_t sep_pos = url.find(PROTOCOL_SEP);
651 if( sep_pos != std::string::npos )
652 {
653 protocol = url.substr(0, sep_pos);
654 path = url.substr(sep_pos + PROTOCOL_SEP.length());
655 }
656 else
657 path = url;
658
659 if( protocol == "canvas" )
660 {
661 CanvasPtr canvas = _canvas.lock();
662 if( !canvas )
663 {
664 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
665 return;
666 }
667
668 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
669 if( !canvas_mgr )
670 {
671 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
672 return;
673 }
674
675 const SGPropertyNode* canvas_node =
676 canvas_mgr->getPropertyRoot()
677 ->getParent()
678 ->getNode( path );
679 if( !canvas_node )
680 {
681 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
682 return;
683 }
684
685 // TODO add support for other means of addressing canvases (eg. by
686 // name)
687 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
688 if( !src_canvas )
689 {
690 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
691 return;
692 }
693
694 setSrcCanvas(src_canvas);
695 }
696 else if( protocol == "http" || protocol == "https" )
697 // TODO check https
698 {
699 _http_request =
700 Canvas::getSystemAdapter()
701 ->getHTTPClient()
702 ->load(url)
703 // TODO handle capture of 'this'
704 ->done(this, &Image::handleImageLoadDone);
705 }
706 else
707 {
708 setImage( Canvas::getSystemAdapter()->getImage(path) );
709 }
710 }
711 }
712
713 //----------------------------------------------------------------------------
setupDefaultDimensions()714 void Image::setupDefaultDimensions()
715 {
716 if( !_src_rect.width() || !_src_rect.height() )
717 {
718 // Show whole image by default
719 _node_src_rect->setBoolValue("normalized", true);
720 _node_src_rect->setFloatValue("right", 1);
721 _node_src_rect->setFloatValue("bottom", 1);
722 }
723
724 if( !_region.width() || !_region.height() )
725 {
726 // Default to image size.
727 // TODO handle showing only part of image?
728 const SGRect<int>& dim = getTextureDimensions();
729 _node->setFloatValue("size[0]", dim.width());
730 _node->setFloatValue("size[1]", dim.height());
731 }
732 }
733
734 //----------------------------------------------------------------------------
getTextureDimensions() const735 SGRect<int> Image::getTextureDimensions() const
736 {
737 CanvasPtr canvas = _src_canvas.lock();
738 SGRect<int> dim(0,0);
739
740 // Use canvas/image dimensions rather than texture dimensions, as they could
741 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
742 if( canvas )
743 {
744 dim.setRight( canvas->getViewWidth() );
745 dim.setBottom( canvas->getViewHeight() );
746 }
747 else if( _texture )
748 {
749 osg::Image* img = _texture->getImage();
750
751 if( img )
752 {
753 dim.setRight( img->s() );
754 dim.setBottom( img->t() );
755 }
756 }
757
758 return dim;
759 }
760
761 //----------------------------------------------------------------------------
setQuad(size_t index,const SGVec2f & tl,const SGVec2f & br)762 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
763 {
764 int i = index * 4;
765 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
766 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
767 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
768 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
769 }
770
771 //----------------------------------------------------------------------------
setQuadUV(size_t index,const SGVec2f & tl,const SGVec2f & br)772 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
773 {
774 int i = index * 4;
775 (*_texCoords)[i + 0].set(tl.x(), tl.y());
776 (*_texCoords)[i + 1].set(br.x(), tl.y());
777 (*_texCoords)[i + 2].set(br.x(), br.y());
778 (*_texCoords)[i + 3].set(tl.x(), br.y());
779 }
780
781 //----------------------------------------------------------------------------
handleImageLoadDone(HTTP::Request * req)782 void Image::handleImageLoadDone(HTTP::Request* req)
783 {
784 // Ignore stale/expired requests
785 if( _http_request != req )
786 return;
787 _http_request.reset();
788
789 if( req->responseCode() != 200 )
790 {
791 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
792 << req->responseReason());
793 return;
794 }
795
796 const std::string ext = SGPath(req->path()).extension(),
797 mime = req->responseMime();
798
799 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
800 " (ext=" << ext << ", MIME=" << mime << ")");
801
802 const std::string& img_data =
803 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
804 osgDB::Registry* reg = osgDB::Registry::instance();
805
806 // First try to detect image type by extension
807 osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
808 if( rw && loadImage(*rw, img_data, *req, "extension") )
809 return;
810
811 // Now try with MIME type
812 rw = reg->getReaderWriterForMimeType(mime);
813 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
814 return;
815
816 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
817 }
818
819 //----------------------------------------------------------------------------
loadImage(osgDB::ReaderWriter & reader,const std::string & data,HTTP::Request & request,const std::string & type)820 bool Image::loadImage( osgDB::ReaderWriter& reader,
821 const std::string& data,
822 HTTP::Request& request,
823 const std::string& type )
824 {
825 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
826
827 std::istringstream data_strm(data);
828 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
829 if( result.success() )
830 {
831 setImage( result.takeImage() );
832 return true;
833 }
834
835 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
836 << result.message());
837
838 return false;
839 }
840
fillRect(const SGRect<int> & rect,const std::string & c)841 void Image::fillRect(const SGRect<int>& rect, const std::string& c)
842 {
843 osg::Vec4 color(1,1,1,1);
844 if(!c.empty() && !parseColor(c, color))
845 return;
846
847 fillRect(rect, color);
848 }
849
fillRow(GLubyte * row,GLuint pixel,GLuint width,GLuint pixelBytes)850 void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes)
851 {
852 GLubyte* dst = row;
853 for (GLuint x = 0; x < width; ++x) {
854 memcpy(dst, &pixel, pixelBytes);
855 dst += pixelBytes;
856 }
857 }
858
intersectRect(const SGRect<int> & a,const SGRect<int> & b)859 SGRect<int> intersectRect(const SGRect<int>& a, const SGRect<int>& b)
860 {
861 SGVec2<int> m1 = max(a.getMin(), b.getMin());
862 SGVec2<int> m2 = min(a.getMax(), b.getMax());
863 return SGRect<int>(m1, m2);
864 }
865
fillRect(const SGRect<int> & rect,const osg::Vec4 & color)866 void Image::fillRect(const SGRect<int>& rect, const osg::Vec4& color)
867 {
868 osg::ref_ptr<osg::Image> image = _texture->getImage();
869 if (!image) {
870 allocateImage();
871 image = _texture->getImage();
872 }
873
874 if (image->getDataVariance() != osg::Object::DYNAMIC) {
875 image->setDataVariance(osg::Object::DYNAMIC);
876 }
877
878 const auto format = image->getInternalTextureFormat();
879
880 auto clippedRect = intersectRect(rect, SGRect<int>(0, 0, image->s(), image->t()));
881 if ((clippedRect.width() == 0) || (clippedRect.height() == 0)) {
882 return;
883 }
884
885 GLubyte* rowData = nullptr;
886 size_t rowByteSize = 0;
887 GLuint pixelWidth = clippedRect.width();
888 GLuint pixel = 0;
889 GLuint pixelBytes = 0;
890
891 switch (format) {
892 case GL_RGBA8:
893 case GL_RGBA:
894 rowByteSize = pixelWidth * 4;
895 rowData = static_cast<GLubyte*>(alloca(rowByteSize));
896
897 // assume litte-endian, so read out backwards, hence when we memcpy
898 // the data, it ends up in RGBA order
899 pixel = color.asABGR();
900 pixelBytes = 4;
901 fillRow(rowData, pixel, pixelWidth, pixelBytes);
902 break;
903
904 case GL_RGB8:
905 case GL_RGB:
906 rowByteSize = pixelWidth * 3;
907 rowData = static_cast<GLubyte*>(alloca(rowByteSize));
908 pixel = color.asABGR();
909 pixelBytes = 3;
910 fillRow(rowData, pixel, pixelWidth, pixelBytes);
911 break;
912
913 default:
914 SG_LOG(SG_IO, SG_WARN, "Image::fillRect: unsupported internal image format:" << format);
915 return;
916 }
917
918 for (int row=clippedRect.t(); row < clippedRect.b(); ++row) {
919 GLubyte* imageData = image->data(clippedRect.l(), row);
920 memcpy(imageData, rowData, rowByteSize);
921 }
922
923 image->dirty();
924 auto c = getCanvas().lock();
925 c->enableRendering(true); // force a repaint
926 }
927
setPixel(int x,int y,const std::string & c)928 void Image::setPixel(int x, int y, const std::string& c)
929 {
930 osg::Vec4 color(1,1,1,1);
931 if(!c.empty() && !parseColor(c, color))
932 return;
933
934 setPixel(x, y, color);
935 }
936
setPixel(int x,int y,const osg::Vec4 & color)937 void Image::setPixel(int x, int y, const osg::Vec4& color)
938 {
939 osg::ref_ptr<osg::Image> image = _texture->getImage();
940 if (!image) {
941 allocateImage();
942 image = _texture->getImage();
943 }
944
945 if (image->getDataVariance() != osg::Object::DYNAMIC) {
946 image->setDataVariance(osg::Object::DYNAMIC);
947 }
948
949 image->setColor(color, x, y);
950 }
951
dirtyPixels()952 void Image::dirtyPixels()
953 {
954 osg::ref_ptr<osg::Image> image = _texture->getImage();
955 if (!image)
956 return;
957 image->dirty();
958 auto c = getCanvas().lock();
959 c->enableRendering(true); // force a repaint
960 }
961
allocateImage()962 void Image::allocateImage()
963 {
964 osg::Image* image = new osg::Image;
965 // default to RGBA
966 image->allocateImage(_node->getIntValue("size[0]"), _node->getIntValue("size[1]"), 1, GL_RGBA, GL_UNSIGNED_BYTE);
967 image->setInternalTextureFormat(GL_RGBA);
968 _texture->setImage(image);
969 }
970
971
getImage() const972 osg::ref_ptr<osg::Image> Image::getImage() const
973 {
974 if (!_texture)
975 return {};
976 return _texture->getImage();
977 }
978
979 } // namespace canvas
980 } // namespace simgear
981