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