1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoImage SoImage.h Inventor/nodes/SoImage.h
35   \brief The SoImage class draws a 2D image on the viewport.
36 
37   \ingroup nodes
38 
39   An image can be specified either by using the image field, or by
40   specifying a filename. If width and or height is specified, the
41   image will be resized to match those values before it is displayed.
42 
43   The current modelview matrix will be used to find the viewport
44   position, and the image is rendered in that position, with
45   z-buffer testing activated.
46 
47   Here's a simple, stand-alone example on how to set up and show an
48   SoImage:
49 
50   \code
51   #include <cstdlib>
52   #include <Inventor/Qt/SoQt.h>
53   #include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
54   #include <Inventor/nodes/SoSeparator.h>
55   #include <Inventor/nodes/SoCamera.h>
56   #include <Inventor/nodes/SoCube.h>
57   #include <Inventor/nodes/SoImage.h>
58 
59   static void
60   mandel(double sr, double si, double width, double height,
61          int bwidth, int bheight, int mult, unsigned char * bmp, int n)
62   {
63     double zr, zr_old, zi, cr, ci;
64     int w;
65 
66     for (int y=0; y<bheight; y++)
67       for (int x=0; x<bwidth; x++) {
68         cr = ((double)(x)/(double)(bwidth))*width+sr;
69         ci = ((double)(y)/(double)(bheight))*height+si;
70         zr = zi = 0.0;
71         for (w = 0; (w < n) && (zr*zr + zi*zi)<n; w++) {
72           zr_old = zr;
73           zr = zr*zr - zi*zi + cr;
74           zi = 2*zr_old*zi + ci;
75         }
76         bmp[y*bwidth+x] = w*mult;
77       }
78   }
79 
80   int
81   main(int argc, char ** argv)
82   {
83     QWidget * mainwin = SoQt::init(argv[0]);
84 
85     SoSeparator * root = new SoSeparator;
86     root->ref();
87 
88     const int IMGWIDTH = 256;
89     const int IMGHEIGHT = 256;
90     unsigned char * img = new unsigned char[IMGWIDTH * IMGHEIGHT];
91     mandel(-0.5, 0.6, 0.025, 0.025, IMGWIDTH, IMGHEIGHT, 1, img, 256);
92 
93     SoImage * nimage = new SoImage;
94     nimage->vertAlignment = SoImage::HALF;
95     nimage->horAlignment = SoImage::CENTER;
96     nimage->image.setValue(SbVec2s(IMGWIDTH, IMGHEIGHT), 1, img);
97 
98     SoCube * cube = new SoCube;
99 
100     root->addChild(cube);
101     root->addChild(nimage);
102 
103     SoQtExaminerViewer * viewer = new SoQtExaminerViewer(mainwin);
104     viewer->setSceneGraph(root);
105     viewer->setTitle("SoImage use");
106     viewer->show();
107 
108     SoCamera * cam = viewer->getCamera();
109     cam->position = SbVec3f(0, 0, 50);
110     cam->focalDistance = 50;
111 
112     SoQt::show(mainwin);
113     SoQt::mainLoop();
114 
115     delete viewer;
116     root->unref();
117     delete img;
118     return 0;
119   }
120   \endcode
121 
122   Note that an SoImage node in the scene graph will have it's
123   positioning / rendering influenced by the current viewport and
124   camera. This has important implications for how to layout your scene
125   graph for the best possible rendering performance. See the note
126   about this issue in the SoText2 class documentation.
127 
128   SoScale nodes can not be used to influence the dimensions of the
129   rendering output of SoImage nodes.
130 
131   <b>FILE FORMAT/DEFAULTS:</b>
132   \code
133     Image {
134         width -1
135         height -1
136         vertAlignment BOTTOM
137         horAlignment LEFT
138         image 0 0 0
139 
140         filename ""
141     }
142   \endcode
143 
144   \since TGS Inventor 2.5
145   \since Coin 1.0
146 */
147 
148 #include <Inventor/nodes/SoImage.h>
149 
150 #ifdef HAVE_CONFIG_H
151 #include <config.h>
152 #endif // HAVE_CONFIG_H
153 
154 #include <Inventor/SbImage.h>
155 #include <Inventor/SbPlane.h>
156 #include <Inventor/SoInput.h>
157 #include <Inventor/SoPrimitiveVertex.h>
158 #include <Inventor/actions/SoGLRenderAction.h>
159 #include <Inventor/actions/SoGetPrimitiveCountAction.h>
160 #include <Inventor/actions/SoRayPickAction.h>
161 #include <Inventor/elements/SoModelMatrixElement.h>
162 #include <Inventor/elements/SoMultiTextureImageElement.h>
163 #include <Inventor/elements/SoViewVolumeElement.h>
164 #include <Inventor/elements/SoViewportRegionElement.h>
165 #include <Inventor/elements/SoGLCacheContextElement.h>
166 #include <Inventor/elements/SoGLLazyElement.h>
167 #include <Inventor/errors/SoDebugError.h>
168 #include <Inventor/errors/SoReadError.h>
169 #include <Inventor/lists/SbStringList.h>
170 #include <Inventor/misc/SoState.h>
171 #include <Inventor/sensors/SoFieldSensor.h>
172 #include <Inventor/system/gl.h>
173 
174 #include "nodes/SoSubNodeP.h"
175 #include "glue/GLUWrapper.h"
176 #include "glue/simage_wrapper.h"
177 
178 
179 /*!
180   \enum SoImage::VertAlignment
181   Vertical alignment for image.
182 */
183 /*!
184   \var SoImage::VertAlignment SoImage::BOTTOM
185   Vertical alignment at bottom of image.
186 */
187 /*!
188   \var SoImage::VertAlignment SoImage::HALF
189   Vertical alignment at center of image.
190 */
191 /*!
192   \var SoImage::VertAlignment SoImage::TOP
193   Vertical alignment at top of image.
194 */
195 /*!
196   \enum SoImage::HorAlignment
197   Horizontal alignment for image.
198 */
199 /*!
200   \var SoImage::HorAlignment SoImage::LEFT
201   Horizontal alignment at left border.
202 */
203 /*!
204   \var SoImage::HorAlignment SoImage::CENTER
205   Horizontal alignment at center of image.
206 */
207 /*!
208   \var SoImage::HorAlignment SoImage::RIGHT
209   Horizontal alignment ar right border.
210 */
211 
212 
213 /*!
214   \var SoSFInt32 SoImage::width
215 
216   If bigger than 0, resize image to this width before rendering.
217   Default value is -1 (ie "don't resize").
218 */
219 /*!
220   \var SoSFInt32 SoImage::height
221 
222   If bigger than 0, resize image to this height before rendering.
223   Default value is -1 (ie "don't resize").
224 */
225 /*!
226   \var SoSFEnum SoImage::vertAlignment
227 
228   Vertical alignment. Default value is SoImage::BOTTOM.
229 */
230 /*!
231   \var SoSFEnum SoImage::horAlignment
232 
233   Horizontal alignment.  Default value is SoImage::LEFT.
234 */
235 /*!
236   \var SoSFImage SoImage::image
237 
238   Inline image data. Default empty.
239 */
240 /*!
241   \var SoSFString SoImage::filename
242 
243   Image filename. Default empty.
244 */
245 
246 
247 // *************************************************************************
248 
249 SO_NODE_SOURCE(SoImage);
250 
251 /*!
252   Constructor.
253 */
SoImage(void)254 SoImage::SoImage(void)
255 {
256   SO_NODE_INTERNAL_CONSTRUCTOR(SoImage);
257 
258 
259   SO_NODE_ADD_FIELD(width, (-1));
260   SO_NODE_ADD_FIELD(height, (-1));
261 
262   SO_NODE_ADD_FIELD(vertAlignment, (SoImage::BOTTOM));
263   SO_NODE_ADD_FIELD(horAlignment, (SoImage::LEFT));
264   SO_NODE_ADD_FIELD(image, (SbVec2s(0,0), 0, NULL));
265   SO_NODE_ADD_FIELD(filename, (""));
266 
267   SO_NODE_DEFINE_ENUM_VALUE(VertAlignment, BOTTOM);
268   SO_NODE_DEFINE_ENUM_VALUE(VertAlignment, HALF);
269   SO_NODE_DEFINE_ENUM_VALUE(VertAlignment, TOP);
270   SO_NODE_SET_SF_ENUM_TYPE(vertAlignment, VertAlignment);
271 
272   SO_NODE_DEFINE_ENUM_VALUE(HorAlignment, LEFT);
273   SO_NODE_DEFINE_ENUM_VALUE(HorAlignment, CENTER);
274   SO_NODE_DEFINE_ENUM_VALUE(HorAlignment, RIGHT);
275   SO_NODE_SET_SF_ENUM_TYPE(horAlignment, HorAlignment);
276 
277   this->readstatus = TRUE;
278   this->transparency = FALSE;
279   this->testtransparency = FALSE;
280 
281   // use field sensor for filename since we will load an image if
282   // filename changes. This is a time-consuming task which should
283   // not be done in notify().
284   this->filenamesensor = new SoFieldSensor(filenameSensorCB, this);
285   this->filenamesensor->setPriority(0);
286   this->filenamesensor->attach(&this->filename);
287   this->resizedimage = new SbImage;
288   this->resizedimagevalid = FALSE;
289 }
290 
291 /*!
292   Destructor.
293 */
~SoImage()294 SoImage::~SoImage()
295 {
296   delete this->resizedimage;
297   delete this->filenamesensor;
298 }
299 
300 // doc from parent
301 void
initClass(void)302 SoImage::initClass(void)
303 {
304   SO_NODE_INTERNAL_INIT_CLASS(SoImage, SO_FROM_INVENTOR_2_5|SO_FROM_COIN_1_0);
305 }
306 
307 // doc from parent
308 void
computeBBox(SoAction * action,SbBox3f & box,SbVec3f & center)309 SoImage::computeBBox(SoAction * action,
310                      SbBox3f & box, SbVec3f & center)
311 {
312   // ignore if node is empty
313   if (this->getSize() == SbVec2s(0,0)) return;
314 
315   SbVec3f v0, v1, v2, v3;
316   // this will cause a cache dependency on the view volume,
317   // model matrix and viewport.
318   this->getQuad(action->getState(), v0, v1, v2, v3);
319 
320   box.makeEmpty();
321   box.extendBy(v0);
322   box.extendBy(v1);
323   box.extendBy(v2);
324   box.extendBy(v3);
325   center = box.getCenter();
326 }
327 
328 // doc from parent
329 void
GLRender(SoGLRenderAction * action)330 SoImage::GLRender(SoGLRenderAction * action)
331 {
332   SbVec2s size, orgsize;
333   int nc;
334   size = this->getSize();
335   if (size == SbVec2s(0,0)) return;
336 
337   const unsigned char * dataptr = this->image.getValue(orgsize, nc);
338   if (dataptr == NULL) return; // no image
339 
340   if (!this->shouldGLRender(action)) return;
341 
342   SoState *state = action->getState();
343   this->testTransparency();
344   if (action->handleTransparency(this->transparency)) return;
345 
346   // evaluate lazy element to enable/disable blending
347   const SoGLLazyElement * elem = (const SoGLLazyElement*)
348     SoLazyElement::getInstance(state);
349   elem->send(state, SoLazyElement::ALL_MASK);
350 
351   const SbViewportRegion & vp = SoViewportRegionElement::get(state);
352   SbVec2s vpsize = vp.getViewportSizePixels();
353 
354   SbVec3f nilpoint = SoImage::getNilpoint(state);
355 
356   int xpos = 0;
357   switch (this->horAlignment.getValue()) {
358   case SoImage::LEFT:
359     xpos = (int)nilpoint[0];
360     break;
361   case SoImage::RIGHT:
362     xpos = (int)nilpoint[0] - size[0];
363     break;
364   case SoImage::CENTER:
365     xpos = (int)nilpoint[0] - (size[0]>>1);
366     break;
367 #if COIN_DEBUG
368   default:
369     SoDebugError::post("SoImage::GLRender",
370                        "value of horAlign field is invalid");
371     break;
372 #endif // COIN_DEBUG
373   }
374 
375   int ypos = 0;
376   switch (this->vertAlignment.getValue()) {
377   case SoImage::TOP:
378     ypos = (int)nilpoint[1] - size[1];
379     break;
380   case SoImage::BOTTOM:
381     ypos = (int)nilpoint[1];
382     break;
383   case SoImage::HALF:
384     ypos = (int)nilpoint[1] - (size[1]>>1);
385     break;
386 #if COIN_DEBUG
387   default:
388     SoDebugError::post("SoImage::GLRender",
389                        "value of vertAlign field is invalid");
390     break;
391 #endif // COIN_DEBUG
392   }
393 
394   GLenum format = GL_LUMINANCE; // init unnecessary, but kills a compiler warning.
395   switch (nc) {
396   case 1:
397     format = GL_LUMINANCE;
398     break;
399   case 2:
400     format = GL_LUMINANCE_ALPHA;
401     break;
402   case 3:
403     format = GL_RGB;
404     break;
405   case 4:
406     format = GL_RGBA;
407     break;
408 #if COIN_DEBUG
409   default:
410     assert(0 && "illegal numComponents");
411     break;
412 #endif
413   }
414 
415   int srcw = size[0];
416   int srch = size[1];
417   int skipx = 0;
418   int skipy = 0;
419 
420   if (xpos >= vpsize[0]) return;
421   else if (xpos < -size[0]) return;
422   else if (xpos < 0) {
423     srcw += xpos;
424     skipx = -xpos;
425     xpos = 0;
426   }
427 
428   if (ypos > vpsize[1]) return;
429   else if (ypos < -size[1]) return;
430   else if (ypos < 0) {
431     srch += ypos;
432     skipy = -ypos;
433     ypos = 0;
434   }
435 
436   if (srcw > vpsize[0] - xpos) {
437     srcw = vpsize[0]-xpos;
438   }
439   if (srch > vpsize[1] - ypos) {
440     srch = vpsize[1]-ypos;
441   }
442 
443   glMatrixMode(GL_MODELVIEW);
444   glPushMatrix();
445   glLoadIdentity();
446   glMatrixMode(GL_PROJECTION);
447   glPushMatrix();
448   glLoadIdentity();
449   glOrtho(0, vpsize[0], 0, vpsize[1], -1.0f, 1.0f);
450 
451   float oldzx, oldzy;
452 
453   if (orgsize != size) { // use glPixelZoom to scale image
454     glGetFloatv(GL_ZOOM_X, &oldzx);
455     glGetFloatv(GL_ZOOM_Y, &oldzy);
456 
457     // calculate pixel zoom value
458     float zx, zy;
459     zx = float(size[0]) / float(orgsize[0]);
460     zy = float(size[1]) / float(orgsize[1]);
461 
462     // update GL
463     glPixelZoom(zx, zy);
464 
465     // adjust glDrawPixels and glPixelStorage parameters to account for zoom
466     srcw = (int) (srcw / zx);
467     srch = (int) (srch / zy);
468     skipx = (int) (skipx / zx);
469     skipy = (int) (skipy / zy);
470 
471     // in case of rounding errors
472     if (skipx + srcw > orgsize[0]) {
473       srcw = orgsize[0] - skipx;
474     }
475     if (skipy + srch > orgsize[1]) {
476       srch = orgsize[1] - skipy;
477     }
478   }
479 
480 
481   GLfloat rpx = xpos >= 0 ? xpos : 0.0f;
482   SbBool offvp = xpos < 0 ? TRUE : FALSE;
483   GLfloat offsetx = xpos >= 0 ? 0.0f : xpos;
484 
485   GLfloat rpy = ypos >= 0 ? ypos : 0.0f;
486   offvp = offvp || ypos < 0 ? TRUE : FALSE;
487   GLfloat offsety = ypos >= 0 ? 0.0f : ypos;
488 
489   glRasterPos3f(rpx, rpy, -nilpoint[2]);
490 
491   if (offvp) { glBitmap(0,0,0,0,offsetx,offsety,NULL); }
492 
493   glPixelStorei(GL_UNPACK_ROW_LENGTH, orgsize[0]);
494   glPixelStorei(GL_UNPACK_SKIP_PIXELS, skipx);
495   glPixelStorei(GL_UNPACK_SKIP_ROWS, skipy);
496   glPixelStorei(GL_PACK_ROW_LENGTH, vpsize[0]);
497   glPixelStorei(GL_PACK_ALIGNMENT, 1);
498   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
499 
500   glDrawPixels(srcw, srch, format, GL_UNSIGNED_BYTE,
501                (const GLvoid*) dataptr);
502 
503   glMatrixMode(GL_PROJECTION);
504   glPopMatrix();
505   glMatrixMode(GL_MODELVIEW);
506   glPopMatrix();
507 
508   if (orgsize != size) {
509     // restore zoom
510     glPixelZoom(oldzx, oldzy);
511   }
512 
513   // restore to default values
514   glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
515   glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
516   glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
517   glPixelStorei(GL_PACK_ROW_LENGTH, 0);
518   glPixelStorei(GL_PACK_ALIGNMENT, 4);
519   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
520 
521   // don't auto cache Image nodes.
522   SoGLCacheContextElement::shouldAutoCache(action->getState(),
523                                            SoGLCacheContextElement::DONT_AUTO_CACHE);
524 }
525 
526 // doc from parent
527 void
rayPick(SoRayPickAction * action)528 SoImage::rayPick(SoRayPickAction * action)
529 {
530   if (this->getSize() == SbVec2s(0,0)) return;
531 
532   if (this->shouldRayPick(action)) {
533     this->computeObjectSpaceRay(action);
534 
535     SbVec3f intersection;
536     SbVec3f barycentric;
537     SbBool front;
538     SbVec3f v0,v1,v2,v3;
539     this->getQuad(action->getState(), v0, v1, v2, v3);
540 
541     if (action->intersect(v0, v1, v2,
542                           intersection, barycentric, front)) {
543 
544       if (action->isBetweenPlanes(intersection))
545         action->addIntersection(intersection);
546     }
547     else if (action->intersect(v0, v2, v3,
548                                intersection, barycentric, front)) {
549 
550       if (action->isBetweenPlanes(intersection))
551         action->addIntersection(intersection);
552     }
553   }
554 }
555 
556 // doc from parent
557 void
getPrimitiveCount(SoGetPrimitiveCountAction * action)558 SoImage::getPrimitiveCount(SoGetPrimitiveCountAction * action)
559 {
560   if (this->getSize() == SbVec2s(0,0)) return;
561 
562   if (this->shouldPrimitiveCount(action)) action->incNumImage();
563 }
564 
565 /*!
566   Will generate a textured quad, representing the image in 3D.
567 */
568 void
generatePrimitives(SoAction * action)569 SoImage::generatePrimitives(SoAction * action)
570 {
571   if (this->getSize() == SbVec2s(0,0)) return;
572 
573   SoState *state = action->getState();
574   state->push();
575 
576   // not quite sure if I should do this, but this will enable
577   // SoCallbackAction to get all data it needs to render
578   // this quad correctly. pederb 19991131
579 
580   // FIXME: We may need to explicitly turn on 2D texturing and turn off
581   // 3D texturing, but what is the correct way of doing that in this
582   // case? (kintel 20011118)
583 
584   SbVec2s size;
585   int nc;
586   const unsigned char * dataptr = this->getImage(size, nc);
587 
588 
589   SoMultiTextureImageElement::set(state, this, 0,
590                                   size, nc, dataptr,
591                                   SoMultiTextureImageElement::CLAMP,
592                                   SoMultiTextureImageElement::CLAMP,
593                                   SoMultiTextureImageElement::DECAL,
594                                   SbVec3f(0,0,0));
595   SbVec3f v0, v1, v2, v3;
596   this->getQuad(action->getState(), v0, v1, v2, v3);
597 
598   SbVec3f n = (v1-v0).cross(v2-v0);
599   n.normalize();
600 
601   this->beginShape(action, SoShape::QUADS);
602   SoPrimitiveVertex vertex;
603   vertex.setNormal(n);
604 
605   vertex.setTextureCoords(SbVec2f(0,0));
606   vertex.setPoint(v0);
607   this->shapeVertex(&vertex);
608 
609   vertex.setTextureCoords(SbVec2f(1,0));
610   vertex.setPoint(v1);
611   this->shapeVertex(&vertex);
612 
613   vertex.setTextureCoords(SbVec2f(1,1));
614   vertex.setPoint(v2);
615   this->shapeVertex(&vertex);
616 
617   vertex.setTextureCoords(SbVec2f(0,1));
618   vertex.setPoint(v3);
619   this->shapeVertex(&vertex);
620 
621   this->endShape();
622 
623   state->pop();
624 }
625 
626 // Documented in superclass.
627 SbBool
readInstance(SoInput * in,unsigned short flags)628 SoImage::readInstance(SoInput * in, unsigned short flags)
629 {
630   // Overridden to load image file.
631 
632   this->filenamesensor->detach();
633   SbBool readOK = inherited::readInstance(in, flags);
634   this->setReadStatus(readOK);
635   if (readOK && !filename.isDefault()) {
636     if (!this->loadFilename()) {
637       SoReadError::post(in, "Could not read image file '%s'",
638                         filename.getValue().getString());
639       this->setReadStatus(FALSE);
640     }
641   }
642   this->filenamesensor->attach(&this->filename);
643   return readOK;
644 }
645 
646 // Documented in superclass.
647 void
notify(SoNotList * list)648 SoImage::notify(SoNotList * list)
649 {
650   SoField *f = list->getLastField();
651   if (f == &this->image) {
652     this->filename.setDefault(TRUE); // write image, not filename
653     this->testtransparency = TRUE;
654     this->resizedimagevalid = FALSE;
655   }
656   else if (f == &this->width || f == &this->height) {
657     this->resizedimagevalid = FALSE;
658   }
659   inherited::notify(list);
660 }
661 
662 /*!
663   Returns \e TRUE if node was read ok.
664 */
665 int
getReadStatus(void)666 SoImage::getReadStatus(void)
667 {
668   return (int) this->readstatus;
669 }
670 
671 /*!
672   Set read status for this node.
673 */
674 void
setReadStatus(SbBool flag)675 SoImage::setReadStatus(SbBool flag)
676 {
677   this->readstatus = flag;
678 }
679 
680 /*!
681   Returns the screen coordinates for the point in (0,0,0) projected
682   onto the screen. The z-value is stored in the third (z) coordinate.
683 */
684 SbVec3f
getNilpoint(SoState * state)685 SoImage::getNilpoint(SoState * state)
686 {
687   SbVec3f nilpoint(0.0f, 0.0f, 0.0f);
688   const SbMatrix & mat = SoModelMatrixElement::get(state);
689   mat.multVecMatrix(nilpoint, nilpoint);
690 
691   const SbViewVolume &vv = SoViewVolumeElement::get(state);
692 
693   // this function will also modify the z-value of nilpoint
694   // according to the view matrix
695   vv.projectToScreen(nilpoint, nilpoint);
696 
697   const SbViewportRegion & vp = SoViewportRegionElement::get(state);
698   SbVec2s vpsize = vp.getViewportSizePixels();
699   nilpoint[0] = nilpoint[0] * float(vpsize[0]);
700   nilpoint[1] = nilpoint[1] * float(vpsize[1]);
701   // change z range from [0,1] to [-1,1]
702   nilpoint[2] *= 2.0f;
703   nilpoint[2] -= 1.0f;
704 
705   return nilpoint;
706 }
707 
708 // Calculates the quad in 3D.
709 void
getQuad(SoState * state,SbVec3f & v0,SbVec3f & v1,SbVec3f & v2,SbVec3f & v3)710 SoImage::getQuad(SoState * state, SbVec3f & v0, SbVec3f & v1,
711                  SbVec3f & v2, SbVec3f & v3)
712 {
713   SbVec3f nilpoint(0.0f, 0.0f, 0.0f);
714   const SbMatrix & mat = SoModelMatrixElement::get(state);
715   mat.multVecMatrix(nilpoint, nilpoint);
716 
717   const SbViewVolume &vv = SoViewVolumeElement::get(state);
718 
719   SbVec3f screenpoint;
720   vv.projectToScreen(nilpoint, screenpoint);
721 
722   const SbViewportRegion & vp = SoViewportRegionElement::get(state);
723   SbVec2s vpsize = vp.getViewportSizePixels();
724 
725   // find normalized width and height of image
726   float nw = (float)this->getSize()[0];
727   nw /= (float)vpsize[0];
728   float nh = (float)this->getSize()[1];
729   nh /= (float)vpsize[1];
730 
731   // need only half the width
732   nw *= 0.5f;
733   nh *= 0.5f;
734 
735   SbVec2f n0, n1, n2, n3;
736 
737   n0 = SbVec2f(screenpoint[0]-nw, screenpoint[1]-nh);
738   n1 = SbVec2f(screenpoint[0]+nw, screenpoint[1]-nh);
739   n2 = SbVec2f(screenpoint[0]+nw, screenpoint[1]+nh);
740   n3 = SbVec2f(screenpoint[0]-nw, screenpoint[1]+nh);
741 
742   switch (this->horAlignment.getValue()) {
743   case SoImage::LEFT:
744     n0[0] += nw;
745     n1[0] += nw;
746     n2[0] += nw;
747     n3[0] += nw;
748     break;
749   case SoImage::RIGHT:
750     n0[0] -= nw;
751     n1[0] -= nw;
752     n2[0] -= nw;
753     n3[0] -= nw;
754     break;
755   case SoImage::CENTER:
756     break;
757   default:
758     assert(0 && "unknown alignment");
759     break;
760   }
761 
762   switch (this->vertAlignment.getValue()) {
763   case SoImage::TOP:
764     n0[1] -= nh;
765     n1[1] -= nh;
766     n2[1] -= nh;
767     n3[1] -= nh;
768     break;
769   case SoImage::BOTTOM:
770     n0[1] += nh;
771     n1[1] += nh;
772     n2[1] += nh;
773     n3[1] += nh;
774     break;
775   case SoImage::HALF:
776     break;
777   default:
778     assert(0 && "unknown alignment");
779     break;
780   }
781 
782   // get distance from nilpoint to camera plane
783   float dist = -vv.getPlane(0.0f).getDistance(nilpoint);
784 
785   // find the four image points in the plane
786   v0 = vv.getPlanePoint(dist, n0);
787   v1 = vv.getPlanePoint(dist, n1);
788   v2 = vv.getPlanePoint(dist, n2);
789   v3 = vv.getPlanePoint(dist, n3);
790 
791   // transform back to object space
792   SbMatrix inv = mat.inverse();
793   inv.multVecMatrix(v0, v0);
794   inv.multVecMatrix(v1, v1);
795   inv.multVecMatrix(v2, v2);
796   inv.multVecMatrix(v3, v3);
797 }
798 
799 // Returns requested on-screen size.
800 SbVec2s
getSize(void) const801 SoImage::getSize(void) const
802 {
803   SbVec2s size;
804   int nc;
805   (void) this->image.getValue(size, nc);
806 
807   if (size[0] == 0 || size[1] == 0) return SbVec2s(0,0);
808 
809   if (this->width.getValue() > 0) {
810     size[0] = this->width.getValue();
811   }
812   if (this->height.getValue() > 0) {
813     size[1] = this->height.getValue();
814   }
815   return size;
816 }
817 
818 const unsigned char *
getImage(SbVec2s & size,int & nc)819 SoImage::getImage(SbVec2s & size, int & nc)
820 {
821   if (this->getSize() == SbVec2s(0,0)) {
822     size = SbVec2s(0,0);
823     nc = 0;
824     return NULL;
825   }
826 
827   if (this->width.getValue() >= 0 || this->height.getValue() >= 0) {
828     if (!this->resizedimagevalid) {
829       SbVec2s orgsize;
830       const unsigned char * orgdata = this->image.getValue(orgsize, nc);
831       SbVec2s newsize = this->getSize();
832 
833       // simage version 1.1.1 has a pretty high quality resize
834       // function. We prefer to use that to avoid using GLU, since
835       // GLU might require a valid GL context for gluScale to work.
836       // Also, there are lots of buggy GLU versions out there.
837       if (simage_wrapper()->available &&
838           simage_wrapper()->versionMatchesAtLeast(1,1,1) &&
839           simage_wrapper()->simage_resize) {
840         unsigned char * result =
841           simage_wrapper()->simage_resize((unsigned char*) orgdata,
842                                           int(orgsize[0]), int(orgsize[1]),
843                                           nc, int(newsize[0]), int(newsize[1]));
844         this->resizedimage->setValue(newsize, nc, result);
845         simage_wrapper()->simage_free_image(result);
846         this->resizedimagevalid = TRUE;
847       }
848       else if (GLUWrapper()->available) {
849         this->resizedimage->setValue(newsize, nc, NULL);
850         const unsigned char * rezdata = this->resizedimage->getValue(newsize, nc);
851         GLenum format;
852         switch (nc) {
853         default: // avoid compiler warnings
854         case 1: format = GL_LUMINANCE; break;
855         case 2: format = GL_LUMINANCE_ALPHA; break;
856         case 3: format = GL_RGB; break;
857         case 4: format = GL_RGBA; break;
858         }
859         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
860         glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
861         glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
862         glPixelStorei(GL_PACK_ROW_LENGTH, 0);
863         glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
864         glPixelStorei(GL_PACK_SKIP_ROWS, 0);
865         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
866         glPixelStorei(GL_PACK_ALIGNMENT, 1);
867 
868         (void)GLUWrapper()->gluScaleImage(format,
869                                           orgsize[0], orgsize[1],
870                                           GL_UNSIGNED_BYTE, (void*) orgdata,
871                                           newsize[0], newsize[1],
872                                           GL_UNSIGNED_BYTE,
873                                           (void*) rezdata);
874         // restore to default
875         glPixelStorei(GL_PACK_ALIGNMENT, 4);
876         glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
877         this->resizedimagevalid = TRUE;
878       }
879 #if COIN_DEBUG
880       else {
881         SoDebugError::postInfo("SoImage::getImage",
882                                "No resize function found.");
883       }
884 #endif // COIN_DEBUG
885     }
886     return this->resizedimage->getValue(size, nc);
887   }
888   return this->image.getValue(size, nc);
889 }
890 
891 //
892 // check image data for transparency
893 //
894 void
testTransparency(void)895 SoImage::testTransparency(void)
896 {
897   if (!this->testtransparency) return;
898   this->testtransparency = FALSE;
899   this->transparency = FALSE;
900   SbVec2s size;
901   int nc;
902   const unsigned char * data = this->image.getValue(size, nc);
903 
904   if (nc == 2 || nc == 4) {
905     int n = size[0] * size[1];
906     const unsigned char * ptr = (unsigned char *) data + nc - 1;
907 
908     while (n) {
909       if (*ptr != 255) break;
910       ptr += nc;
911       n--;
912     }
913     this->transparency = n > 0;
914   }
915 }
916 
917 //
918 // Called from readInstance() or when user changes the
919 // filename field.
920 //
921 SbBool
loadFilename(void)922 SoImage::loadFilename(void)
923 {
924   SbBool retval = FALSE;
925   if (this->filename.getValue().getLength()) {
926     SbImage tmpimage;
927     const SbStringList & sl = SoInput::getDirectories();
928     if (tmpimage.readFile(this->filename.getValue(),
929                           sl.getArrayPtr(), sl.getLength())) {
930       int nc;
931       SbVec2s size;
932       const unsigned char * bytes = tmpimage.getValue(size, nc);
933       // disable notification on image while setting data from filename
934       // as a notify will cause a filename.setDefault(TRUE).
935       SbBool oldnotify = this->image.enableNotify(FALSE);
936       this->image.setValue(size, nc, bytes);
937       this->image.enableNotify(oldnotify);
938       this->testtransparency = TRUE;
939       retval = TRUE;
940     }
941   }
942   this->image.setDefault(TRUE); // write filename, not image
943   return retval;
944 }
945 
946 //
947 // called when filename changes
948 //
949 void
filenameSensorCB(void * data,SoSensor *)950 SoImage::filenameSensorCB(void * data, SoSensor *)
951 {
952   SoImage * thisp = (SoImage*) data;
953   thisp->setReadStatus(TRUE);
954   if (thisp->filename.getValue().getLength() &&
955       !thisp->loadFilename()) {
956     SoDebugError::postWarning("SoImage::filenameSensorCB",
957                               "could not read image file '%s'",
958                               thisp->filename.getValue().getString());
959     thisp->setReadStatus(FALSE);
960   }
961 }
962