Library of Assembled Shared Sources
image.cpp
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2011 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#include "lass_common.h"
44#include "image.h"
45#include "binary_i_file.h"
46#include "binary_o_file.h"
47#include "file_attribute.h"
53#include "../num/num_cast.h"
54
55#include <cstddef>
56
57#define LASS_IO_IMAGE_ENFORCE_SAME_SIZE(a, b)\
58 *LASS_UTIL_IMPL_MAKE_ENFORCER(\
59 ::lass::util::impl::TruePredicate,\
60 ::lass::util::impl::DefaultRaiser,\
61 ((a).rows() == (b).rows() && (a).cols() == (b).cols()),\
62 int(0),\
63 "Images '" LASS_STRINGIFY(a) "' and '" LASS_STRINGIFY(b) "' have different size in '" LASS_HERE "'.")
64
65#if LASS_COMPILER_TYPE == LASS_COMPILER_TYPE_MSVC
66# pragma warning(disable: 4351) // new behavior: elements of array 'array' will be default initialized
67#endif
68
69namespace lass
70{
71namespace io
72{
73namespace impl
74{
75 class Bytes4
76 {
77 public:
78 Bytes4(): values_() {}
79 num::Tuint8 operator[](size_t k) const { LASS_ASSERT(k < 4); return values_[k]; }
80 num::Tuint8& operator[](size_t k) { LASS_ASSERT(k < 4); return values_[k]; }
81 const num::Tuint8* get() const { return values_; }
82 num::Tuint8* get() { return values_; }
83 bool operator==(const Bytes4& other) const { return std::equal(values_, values_ + 4, other.values_); }
84 private:
85 num::Tuint8 values_[4];
86 };
87}
88
89Image::TFileFormats Image::fileFormats_ = Image::fillFileFormats();
90num::Tuint32 Image::magicLass_ = 0x7373616c; // 'lass' in little endian
91std::string Image::magicRadiance_ = "#?RADIANCE\n";
92num::Tint32 Image::magicIgi_ = 66613373;
93
94// --- public --------------------------------------------------------------------------------------
95
96/** Default constructor. empty image.
97 */
99 colorSpace_(defaultColorSpace()),
100 rows_(0),
101 cols_(0),
102 raster_()
103{
104}
105
106
107
108/** Construct image of given width and height.
109 */
110Image::Image(size_t rows, size_t cols):
111 colorSpace_(defaultColorSpace()),
112 rows_(0),
113 cols_(0),
114 raster_()
115{
116 const TPixel black;
117 resize(rows, cols);
118 std::fill(raster_.begin(), raster_.end(), black);
119}
120
121
122
123/** Construct image from given file.
124 */
125Image::Image(const std::string& path):
126 colorSpace_(defaultColorSpace()),
127 rows_(0),
128 cols_(0),
129 raster_()
130{
131 open(path);
132}
133
134
135
136#if LASS_HAVE_WCHAR_SUPPORT
137
138/** Construct image from given file.
139 */
140Image::Image(const std::wstring& path):
141 colorSpace_(defaultColorSpace()),
142 rows_(0),
143 cols_(0),
144 raster_()
145{
146 open(path);
147}
148
149#endif
150
151
152
153#if LASS_HAVE_STD_FILESYSTEM
154
155/** Construct image from given file.
156 */
157Image::Image(const std::filesystem::path& path):
158 colorSpace_(defaultColorSpace()),
159 rows_(0),
160 cols_(0),
161 raster_()
162{
163 open(path);
164}
165
166#endif
167
168
169
170/** Copy constructor
171 */
172Image::Image(const Image& other):
173 colorSpace_(other.colorSpace_),
174 rows_(other.rows_),
175 cols_(other.cols_),
176 raster_(other.raster_)
177{
178}
179
180
181
182/** Destructor
183 */
185{
186}
187
188
189
190// --- PUBLIC METHODS ----------------------------------------------------------
191
192/** Reset image to empty image.
193 */
195{
196 Image temp;
197 swap(temp);
198}
199
200
201
202/** Reset image to (black) image of given width.
203 */
204void Image::reset(size_t rows, size_t cols)
205{
206 Image temp(rows, cols);
207 swap(temp);
208}
209
210
211
212/** Reset image to the one in the given file.
213 */
214void Image::reset(const std::string& path)
215{
216 Image temp(path);
217 swap(temp);
218}
219
220
221
222#if LASS_HAVE_WCHAR_SUPPORT
223
224/** Reset image to the one in the given file.
225 */
226void Image::reset(const std::wstring& path)
227{
228 Image temp(path);
229 swap(temp);
230}
231
232#endif
233
234
235
236#if LASS_HAVE_STD_FILESYSTEM
237
238/** Reset image to the one in the given file.
239 */
240void Image::reset(const std::filesystem::path& path)
241{
242 Image temp(path);
243 swap(temp);
244}
245
246#endif
247
248
249
250/** Reset image to copy of given image.
251 */
252void Image::reset(const Image& other)
253{
254 Image temp(other);
255 swap(temp);
256}
257
258
259
260/** Open image from file
261 */
262void Image::open(const std::string& path)
263{
264 BinaryIFile file(path);
265 if (!file)
266 {
267 LASS_THROW("could not open file to read.");
268 }
269 open(file, fileExtension(path));
270}
271
272
273
274/** Open image from binary stream
275 */
276void Image::open(BinaryIStream& stream, const std::string& formatTag)
277{
278 Image temp;
279 TFileOpener opener = findFormat(formatTag).open;
280 if (!opener)
281 {
282 LASS_THROW_EX(BadFormat, "cannot open images in file format '" << formatTag << "'.");
283 }
284
285 (temp.*opener)(stream);
286
287 if (stream.good())
288 {
289 swap(temp);
290 }
291 else if (stream.eof())
292 {
293 LASS_THROW_EX(BadFormat, "tried to read past end of file.");
294 }
295 else
296 {
297 LASS_THROW_EX(BadFormat, "unknown failure in file.");
298 }
299}
300
301
302
303/** Save image to file
304 */
305void Image::save(const std::string& path)
306{
307 BinaryOFile file(path);
308 if (!file)
309 {
310 LASS_THROW("could not open file to write.");
311 }
312 save(file, fileExtension(path));
313}
314
315
316
317/** Save image to file
318 */
319void Image::save(BinaryOStream& stream, const std::string& formatTag)
320{
321 TFileSaver saver = findFormat(formatTag).save;
322 if (!saver)
323 {
324 LASS_THROW_EX(BadFormat, "cannot save images in file format '" << formatTag << "'.");
325 }
326
327 try
328 {
329 (this->*saver)(stream);
330 }
331 catch (const num::BadNumCast&)
332 {
333 LASS_THROW_EX(BadFormat, "image size is too large for this format");
334 }
335
336 if (stream.eof())
337 {
338 LASS_THROW_EX(BadFormat, "tried to write past end of file.");
339 }
340 if (!stream.good())
341 {
342 LASS_THROW_EX(BadFormat, "unknown failure in file.");
343 }
344}
345
346
347
348#if LASS_HAVE_WCHAR_SUPPORT
349
350/** Open image from binary stream
351 */
352void Image::open(const std::wstring& path)
353{
354 BinaryIFile file(path);
355 if (!file)
356 {
357 LASS_THROW("could not open file to read.");
358 }
359 open(file, util::wcharToUtf8(fileExtension(path)));
360}
361
362
363
364/** Save image to file
365 */
366void Image::save(const std::wstring& path)
367{
368 BinaryOFile file(path);
369 if (!file)
370 {
371 LASS_THROW("could not open file to write.");
372 }
373 save(file, util::wcharToUtf8(fileExtension(path)));
374}
375
376#endif
377
378
379
380#if LASS_HAVE_STD_FILESYSTEM
381
382/** Open image from binary stream
383 */
384void Image::open(const std::filesystem::path& path)
385{
386 BinaryIFile file(path);
387 if (!file)
388 {
389 LASS_THROW("could not open file to read.");
390 }
391 open(file, fileExtension(path.string()));
392}
393
394
395
396/** Save image to file
397 */
398void Image::save(const std::filesystem::path& path)
399{
400 BinaryOFile file(path);
401 if (!file)
402 {
403 LASS_THROW("could not open file to write.");
404 }
405 save(file, fileExtension(path.string()));
406}
407
408#endif
409
410
411/** Copy other into this image.
412 */
414{
415 Image temp(other);
416 swap(temp);
417 return *this;
418}
419
420
421
422/** swap two images
423 */
424void Image::swap(Image& other)
425{
426 std::swap(colorSpace_, other.colorSpace_);
427 std::swap(rows_, other.rows_);
428 std::swap(cols_, other.cols_);
429 raster_.swap(other.raster_);
430}
431
432
433
434/** Return const pixel at position row, col.
435 * @param row row of pixel, y coordinate.
436 * @param col column of pixel, x coordinate.
437 */
438const Image::TPixel& Image::operator()(size_t row, size_t col) const
439{
440 LASS_ASSERT(row < rows_ && col < cols_);
441 return raster_[flatIndex(row, col)];
442}
443
444
445
446/** Return reference to pixel at position row, col.
447 * @param row row of pixel, y coordinate.
448 * @param col column of pixel, x coordinate.
449 */
450Image::TPixel& Image::operator()(size_t row, size_t col)
451{
452 LASS_ASSERT(row < rows_ && col < cols_);
453 return raster_[flatIndex(row, col)];
454}
455
456
457
458/** Return const pixel at position row, col. position is wrapped around.
459 * @param row row of pixel, y coordinate.
460 * @param col column of pixel, x coordinate.
461 */
462const Image::TPixel& Image::at(TSignedSize row, TSignedSize col) const
463{
464 const size_t i = num::mod(row, rows_);
465 const size_t j = num::mod(col, cols_);
466 return raster_[flatIndex(i, j)];
467}
468
469
470
471/** Return reference to pixel at position row, col. position is wrapped around.
472 * @param row row of pixel, y coordinate.
473 * @param col column of pixel, x coordinate.
474 */
475Image::TPixel& Image::at(TSignedSize row, TSignedSize col)
476{
477 const size_t i = num::mod(row, rows_);
478 const size_t j = num::mod(col, cols_);
479 return raster_[flatIndex(i, j)];
480}
481
482
483
484/** Return const data block.
485 */
486const Image::TPixel* Image::data() const
487{
488 return &raster_[0];
489}
490
491
492
493/** Return data block.
494 */
495Image::TPixel* Image::data()
496{
497 return &raster_[0];
498}
499
500
501
502/** Return colorSpace of image data
503 */
504const Image::ColorSpace& Image::colorSpace() const
505{
506 return colorSpace_;
507}
508
509
510
511/** Return colorSpace of image data
512 *
513 * You can change the parameters of the current color space without transforming
514 * the pixels. This is most useful if you know that the pixels are in a different
515 * color space that the current one is set to. For example, if you know the pixels
516 * have a gamma correction 2.2 applied, but the this->colorSpace().gamma == 1.
517 */
518Image::ColorSpace& Image::colorSpace()
519{
520 return colorSpace_;
521}
522
523
524
525/** Transform the colors from the current color spacer to @a destColorSpace.
526 *
527 * The transformation consists of three stages:
528 * @arg linearise pixels if gamma of current color space != 1
529 * @arg transform colors to linearised destination colour space
530 * @arg apply gamma correction if @a destColorSpace.gamma != 1
531 *
532 * For the color transformation, two transformations are concatenated.
533 * Both of them will also perform a chromatic adaption transform if
534 * the white point of either color space is not the equal energy one (E):
535 * @arg from source RGB to XYZ
536 * @arg from XYZ to dest RGB
537 */
538void Image::transformColors(const ColorSpace& newColorSpace)
539{
540 typedef prim::Transformation3D<TValue> TTransformation;
541
542 struct Impl
543 {
544 static const TTransformation rgb2xyz(const ColorSpace& colorSpace)
545 {
546 typedef TTransformation::TVector TVector;
547 const ColorSpace& C = colorSpace;
548 TValue primaries[16] =
549 {
550 C.red.x, C.green.x, C.blue.x, 0,
551 C.red.y, C.green.y, C.blue.y, 0,
552 1 - C.red.x - C.red.y, 1 - C.green.x - C.green.y, 1 - C.blue.x - C.blue.y, 0,
553 0, 0, 0, 1
554 };
555 TTransformation M(primaries, primaries + 16);
556 const TVector w(C.white.x / C.white.y, 1, (1 - C.white.x - C.white.y) / C.white.y);
557 const TVector S = transform(w, M.inverse());
558 M = concatenate(TTransformation::scaler(S), M);
559
560 TValue bradford[16] =
561 {
562 0.8951f, 0.2664f, -0.1614f, 0.f,
563 -0.7502f, 1.7135f, 0.0367f, 0.f,
564 0.0389f, -0.0685f, 1.0296f, 0.f,
565 0.f, 0.f, 0.f, 1.f
566 };
567 const TTransformation B(bradford, bradford + 16);
568 const TVector sw = transform(w, B);
569 TTransformation M_cat = concatenate(B, TTransformation::scaler(sw.reciprocal()));
570 M_cat = concatenate(M_cat, B.inverse());
571
572 return concatenate(M, M_cat);
573 }
574 };
575
576 const TTransformation A = Impl::rgb2xyz(colorSpace_);
577 const TTransformation B = Impl::rgb2xyz(newColorSpace);
578 const TTransformation C = concatenate(A, B.inverse());
579
580 if (colorSpace_.gamma != 1)
581 {
582 filterGamma(1 / colorSpace_.gamma);
583 }
584 for (TRaster::iterator i = raster_.begin(); i != raster_.end(); ++i)
585 {
586 *i = transform(*i, C);
587 }
588 if (newColorSpace.gamma != 1)
589 {
590 filterGamma(newColorSpace.gamma);
591 }
592 colorSpace_ = newColorSpace;
593 colorSpace_.isFromFile = false;
594}
595
596
597
598/** Return height of image.
599 */
600size_t Image::rows() const
601{
602 return rows_;
603}
604
605
606
607/** Return width of image.
608 */
609size_t Image::cols() const
610{
611 return cols_;
612}
613
614
615
616/** Return true if image is empty (no data)
617 */
618bool Image::isEmpty() const
619{
620 return raster_.empty();
621}
622
623
624
625/** this = this over other
626 */
627void Image::over(const Image& other)
628{
629 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
630 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::over);
631}
632
633
634
635/** this = this in other
636 */
637void Image::in(const Image& other)
638{
639 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
640 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::in);
641}
642
643
644
645/** this = this out other
646 */
647void Image::out(const Image& other)
648{
649 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
650 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::out);
651}
652
653
654
655/** this = this atop other
656 */
657void Image::atop(const Image& other)
658{
659 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
660 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::atop);
661}
662
663
664
665/** this = this through other
666 */
667void Image::through(const Image& other)
668{
669 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
670 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::through);
671}
672
673
674
675/** this = other over this
676 */
677void Image::rover(const Image& other)
678{
679 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
680 std::transform(other.raster_.begin(), other.raster_.end(), raster_.begin(), raster_.begin(), prim::over);
681}
682
683
684
685/** this = other in this
686 */
687void Image::rin(const Image& other)
688{
689 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
690 std::transform(other.raster_.begin(), other.raster_.end(), raster_.begin(), raster_.begin(), prim::in);
691}
692
693
694
695/** this = other out this
696 */
697void Image::rout(const Image& other)
698{
699 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
700 std::transform(other.raster_.begin(), other.raster_.end(), raster_.begin(), raster_.begin(), prim::out);
701}
702
703
704
705/** this = other atop this
706 */
707void Image::ratop(const Image& other)
708{
709 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
710 std::transform(other.raster_.begin(), other.raster_.end(), raster_.begin(), raster_.begin(), prim::atop);
711}
712
713
714
715/** this = other through this
716 */
717void Image::rthrough(const Image& other)
718{
719 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
720 std::transform(other.raster_.begin(), other.raster_.end(), raster_.begin(), raster_.begin(), prim::through);
721}
722
723
724
725/** this = this plus other = other plus this
726 */
727void Image::plus(const Image& other)
728{
729 LASS_IO_IMAGE_ENFORCE_SAME_SIZE(*this, other);
730 std::transform(raster_.begin(), raster_.end(), other.raster_.begin(), raster_.begin(), prim::plus);
731}
732
733
734
735/** clamp all negative pixel components to zero.
736 */
738{
739 const TRaster::iterator last = raster_.end();
740 for (TRaster::iterator i = raster_.begin(); i != last; ++i)
741 {
742 i->r = std::max(i->r, 0.f);
743 i->g = std::max(i->g, 0.f);
744 i->b = std::max(i->b, 0.f);
745 i->a = std::max(i->a, 0.f);
746 }
747}
748
749
750
751/** Apply a median filter on image.
752 * @param boxSize size of box filter, must be odd.
753 *
754 * - Filters only pixels with alphachannel != 0.
755 * - We use the vector median filter for colour images, as described on page
756 * in GAUCH J. M. (1998). Noise Removal and contrast enhancement. In:
757 * Sangwine S. J. & Horne R. E. N. (Eds.) The Colour Image Processing
758 * Handbook. London, Chapman & Hall, 149-162.
759 */
760void Image::filterMedian(size_t boxSize)
761{
762 if (boxSize <= 1)
763 {
764 return;
765 }
766 if ((boxSize & 0x1) == 0)
767 {
768 LASS_THROW("boxSize '" << boxSize << "' isn't odd as requested.");
769 }
770
771 const size_t boxArea = boxSize * boxSize;
772 const size_t boxRadius = (boxSize - 1) / 2;
773 const size_t invalidCandidate = boxArea;
774
775 TRaster box(boxArea);
776 std::vector<float> distances(boxArea);
777
778 for (size_t y0 = 0; y0 < rows_; ++y0)
779 {
780 const size_t yFirst = std::max(y0, boxRadius) - boxRadius;
781 const size_t yLast = std::min(y0 + boxRadius + 1, rows_);
782 LASS_ASSERT(yLast - yFirst <= boxSize);
783 for (size_t x0 = 0; x0 < cols_; ++x0)
784 {
785 TPixel& center = (*this)(y0, x0);
786 if (center.a == TNumTraits::zero)
787 {
788 continue; // no filtering on pixels with alphachannel == 0
789 }
790
791 const size_t xFirst = std::max(x0, boxRadius) - boxRadius;
792 const size_t xLast = std::min(x0 + boxRadius + 1, cols_);
793 LASS_ASSERT(xLast - xFirst <= boxSize);
794
795 // fill filterbox
796 //
797 size_t pixelsInBox = 0;
798 size_t candidate = invalidCandidate;
799 for (size_t y = yFirst; y < yLast; ++y)
800 {
801 for (size_t x = xFirst; x < xLast; ++x)
802 {
803 const TPixel& pixel = (*this)(y0, x0);
804 if (pixel.a != TNumTraits::zero)
805 {
806 box[pixelsInBox] = pixel;
807 if (x == x0 && y == y0)
808 {
809 candidate = pixelsInBox;
810 }
811 ++pixelsInBox;
812 }
813 }
814 }
815
816 // get median in filterbox
817 //
818 LASS_ASSERT(pixelsInBox > 0 && pixelsInBox <= boxArea);
819 LASS_ASSERT(candidate != invalidCandidate && candidate < pixelsInBox);
820 for (size_t i = 0; i < pixelsInBox; ++i)
821 {
822 distances[i] = 0.0;
823 for (size_t j = 0; j < pixelsInBox; ++j)
824 {
825 distances[i] += prim::distance(box[i], box[j]);
826 }
827 }
828 for (size_t i = 0; i < pixelsInBox; ++i)
829 {
830 if (distances[i] < distances[candidate])
831 {
832 candidate = i;
833 }
834 }
835
836 LASS_ASSERT(candidate < pixelsInBox);
837 center = box[candidate];
838 }
839 }
840}
841
842
843
844/** apply gamma correction to image
845 */
846void Image::filterGamma(TParam gammaExponent)
847{
848 const size_t size = raster_.size();
849 for (size_t i = 0; i < size; ++i)
850 {
851 raster_[i] = raster_[i].gammaCorrected(gammaExponent);
852 }
853 colorSpace_.gamma *= gammaExponent;
854}
855
856
857
858/** apply exposure to image
859 */
860void Image::filterExposure(TParam exposureTime)
861{
862 const size_t size = raster_.size();
863 for (size_t i = 0; i < size; ++i)
864 {
865 raster_[i] = raster_[i].exposed(exposureTime);
866 }
867}
868
869
870
871/** apply exposure to image
872 */
873void Image::filterInverseExposure(TParam exposureTime)
874{
875 const size_t size = raster_.size();
876 for (size_t i = 0; i < size; ++i)
877 {
878 raster_[i] = raster_[i].invExposed(exposureTime);
879 }
880}
881
882
883
884// --- protected -----------------------------------------------------------------------------------
885
886
887
888
889// --- private -------------------------------------------------------------------------------------
890
891/** (re)allocate data chunck
892 */
893size_t Image::resize(size_t rows, size_t cols)
894{
895 const size_t size = rows * cols;
896 raster_.resize(size);
897 rows_ = rows;
898 cols_ = cols;
899 return size;
900}
901
902
903
904/** Open LASS Raw file.
905 */
906BinaryIStream& Image::openLass(BinaryIStream& stream)
907{
908 HeaderLass header;
909 header.readFrom(stream);
910 if (!stream || header.lass != magicLass_ || header.version > 3)
911 {
912 LASS_THROW_EX(BadFormat, "not a LASS RAW version 1 - 3 file.");
913 }
914
915 EndiannessSetter(stream, num::littleEndian);
916
917 if (header.version >= 2)
918 {
919 for (size_t i = 0; i < numChromaticities; ++i)
920 {
921 num::Tfloat32 x, y;
922 stream >> x >> y;
923 colorSpace_[i] = TChromaticity(x, y);
924 }
925 colorSpace_.isFromFile = true;
926 }
927 else
928 {
929 colorSpace_ = defaultColorSpace();
930 }
931
932 if (header.version >= 3)
933 {
934 num::Tfloat32 g;
935 stream >> g;
936 colorSpace_.gamma = g;
937 }
938
939 resize(header.rows, header.cols);
940 num::Tfloat32 r, g, b, a;
941 for (TRaster::iterator i = raster_.begin(); i != raster_.end(); ++i)
942 {
943 stream >> r >> g >> b >> a;
944 i->r = r;
945 i->g = g;
946 i->b = b;
947 i->a = a;
948 }
949
950 return stream;
951}
952
953
954
955/** Open TARGA file.
956 */
957BinaryIStream& Image::openTarga(BinaryIStream& stream)
958{
959 HeaderTarga header;
960 header.readFrom(stream);
961 colorSpace_ = defaultColorSpace();
962 colorSpace_.gamma = 2.2f;
963 resize(header.imageHeight, header.imageWidth);
964
965 switch (header.imageType)
966 {
967 case 2:
968 case 10:
969 openTargaTrueColor(stream, header);
970 break;
971 default:
972 LASS_THROW_EX(BadFormat, "unsupported image type '" << static_cast<unsigned>(header.imageType) << "'");
973 break;
974 };
975
976 return stream;
977}
978
979
980
981/** Open Type 2 and 11 TARGA file
982 */
983BinaryIStream& Image::openTargaTrueColor(BinaryIStream& stream, const HeaderTarga& header)
984{
985 const TValue scale = num::inv(255.f);
986
987 std::size_t numBytes = 0;
988 switch (header.imagePixelSize)
989 {
990 case 24:
991 numBytes = 3;
992 break;
993 case 32:
994 numBytes = 4;
995 break;
996 default:
997 LASS_THROW_EX(BadFormat, "image pixel size '" << header.imagePixelSize << "' not supported.");
998 };
999
1000 LASS_ASSERT(cols_ == header.imageWidth);
1001 const int xBegin = header.flipHorizontalFlag() ? static_cast<int>(cols_) - 1 : 0;
1002 const int xEnd = header.flipHorizontalFlag() ? -1 : static_cast<int>(cols_);
1003 const int xDelta = header.flipHorizontalFlag() ? -1 : 1;
1004
1005 LASS_ASSERT(rows_ == header.imageHeight);
1006 const int yBegin = header.flipVerticalFlag() ? 0 : static_cast<int>(rows_) - 1;
1007 const int yEnd = header.flipVerticalFlag() ? static_cast<int>(rows_) : -1;
1008 const int yDelta = header.flipVerticalFlag() ? 1 : -1;
1009
1010 stream.seekg(header.idLength + header.colorMapLength * header.colorMapEntrySize, std::ios_base::cur);
1011 std::vector<impl::Bytes4> buffer(cols_);
1012 for (int y = yBegin; y != yEnd; y += yDelta)
1013 {
1014 if (header.imageType == 10)
1015 {
1016 // decode rle
1017 //
1018 unsigned x = 0;
1019 while (x < cols_)
1020 {
1021 num::Tuint8 code;
1022 stream >> code;
1023 const num::Tuint8 packetSize = static_cast<num::Tuint8>((code & 0x7f) + 1);
1024 impl::Bytes4 bytes;
1025 if (code & 0x80)
1026 {
1027 stream.read(bytes.get(), numBytes);
1028 for (num::Tuint8 i = 0; i < packetSize; ++i)
1029 {
1030 LASS_ASSERT(x < cols_);
1031 buffer[x++] = bytes;
1032 }
1033 }
1034 else
1035 {
1036 for (num::Tuint8 i = 0; i < packetSize; ++i)
1037 {
1038 stream.read(bytes.get(), numBytes);
1039 LASS_ASSERT(x < cols_);
1040 buffer[x++] = bytes;
1041 }
1042 }
1043 }
1044 }
1045 else
1046 {
1047 LASS_ASSERT(header.imageType == 2);
1048 for (unsigned x = 0; x < cols_; ++x)
1049 {
1050 stream.read(buffer[x].get(), numBytes);
1051 }
1052 }
1053
1054 // decode scanline
1055 //
1056 LASS_ASSERT(y >= 0 && static_cast<size_t>(y) < rows_);
1057 TPixel* pixel = &raster_[static_cast<size_t>(y) * cols_];
1058 for (int x = xBegin; x != xEnd; x += xDelta)
1059 {
1060 LASS_ASSERT(x >= 0 && static_cast<size_t>(x) < cols_);
1061 const impl::Bytes4& bytes = buffer[static_cast<size_t>(x)];
1062 pixel->r = static_cast<TValue>(bytes[2]) * scale;
1063 pixel->g = static_cast<TValue>(bytes[1]) * scale;
1064 pixel->b = static_cast<TValue>(bytes[0]) * scale;
1065 pixel->a = numBytes == 3 ? TNumTraits::one : static_cast<TValue>(bytes[3]) * scale;
1066 ++pixel;
1067 }
1068 }
1069
1070 return stream;
1071}
1072
1073
1074
1075BinaryIStream& Image::openRadianceHdr(BinaryIStream& stream)
1076{
1077 HeaderRadianceHdr header;
1078 header.readFrom(stream);
1079 if (!stream)
1080 {
1081 LASS_THROW_EX(BadFormat, "syntax error in RADIANCE HDR header.");
1082 }
1083
1084 if (header.isRgb)
1085 {
1086 for (size_t i = 0; i < numChromaticities; ++i)
1087 {
1088 colorSpace_[i] = TChromaticity(header.primaries[2 * i], header.primaries[2 * i + 1]);
1089 }
1090 }
1091 else
1092 {
1093 colorSpace_.red = TChromaticity(1, 0);
1094 colorSpace_.green = TChromaticity(0, 1);
1095 colorSpace_.blue = TChromaticity(0, 0);
1096 colorSpace_.white = TChromaticity(1.f / 3, 1.f / 3);
1097 }
1098 colorSpace_.gamma = 1;
1099 colorSpace_.isFromFile = true;
1100
1101 float exponents[256];
1102 exponents[0] = 0.f;
1103 for (int i = 1; i < 256; ++i)
1104 {
1105 exponents[i] = ::ldexpf(1.f, i - 128 - 8);
1106 }
1107 float inverseCorrections[3];
1108 for (size_t i = 0; i < 3; ++i)
1109 {
1110 inverseCorrections[i] = num::inv(header.exposure * header.colorCorr[i]);
1111 }
1112
1113 resize(header.height, header.width);
1114
1115 const std::ptrdiff_t firstY = !header.yIncreasing ? 0 : (static_cast<std::ptrdiff_t>(header.height) - 1);
1116 const std::ptrdiff_t lastY = !header.yIncreasing ? static_cast<std::ptrdiff_t>(header.height) : -1;
1117 const std::ptrdiff_t deltaY = !header.yIncreasing ? 1 : -1;
1118 const std::ptrdiff_t firstX = header.xIncreasing ? 0 : (static_cast<std::ptrdiff_t>(header.width) - 1);
1119 const std::ptrdiff_t lastX = header.xIncreasing ? static_cast<std::ptrdiff_t>(header.width) : -1;
1120 const std::ptrdiff_t deltaX = header.xIncreasing ? 1 : -1;
1121
1122 std::vector<impl::Bytes4> buffer(header.width);
1123 size_t rleCount = 0;
1124 size_t rleCountByte = 0;
1125
1126 for (std::ptrdiff_t y = firstY; y != lastY; y += deltaY)
1127 {
1128 // unpack scanline to buffer
1129 //
1130 for (std::ptrdiff_t x = firstX; x != lastX; /* increment in loop */ )
1131 {
1132 impl::Bytes4 rgbe, previous;
1133 stream.read(rgbe.get(), 4);
1134 if (rgbe[0] == 2 && rgbe[1] == 2 && (rgbe[2] & 0x80) == 0)
1135 {
1136 // new rle
1137 //
1138 const std::ptrdiff_t lineLength = rgbe[2] * 256 + rgbe[3];
1139 LASS_ASSERT(lineLength >= 0 && lineLength < 32768);
1140 const std::ptrdiff_t lastX2 = x + lineLength * deltaX;
1141 LASS_ASSERT((lastX - lastX2) * deltaX >= 0);
1142 for (size_t k = 0; k < 4; ++k)
1143 {
1144 std::ptrdiff_t x2 = x;
1145 while (x2 != lastX2)
1146 {
1147 num::Tuint8 spanField = 0, value = 0;
1148 stream >> spanField;
1149 const bool isHomogenousSpan = spanField > 128;
1150 const size_t spanSize = isHomogenousSpan ? spanField & 0x7f : spanField;
1151 if (isHomogenousSpan)
1152 {
1153 stream >> value;
1154 }
1155 for (size_t i = 0; i < spanSize; ++i)
1156 {
1157 if (!isHomogenousSpan)
1158 {
1159 stream >> value;
1160 }
1161 LASS_ASSERT(x2 >= 0 && x2 != lastX2);
1162 buffer[static_cast<size_t>(x2)][k] = value;
1163 x2 += deltaX;
1164 }
1165 }
1166 }
1167 x = lastX2;
1168 }
1169 else
1170 {
1171 // no rle or old rle
1172 //
1173 if (rgbe[0] == 1 && rgbe[1] == 1 && rgbe[2] == 1)
1174 {
1175 LASS_ASSERT(rleCountByte < sizeof(rleCount));
1176 rleCount |= static_cast<size_t>(rgbe[3]) << (8 * rleCountByte);
1177 ++rleCountByte;
1178 }
1179 else
1180 {
1181 if (rleCount > 0)
1182 {
1183 for (size_t k = rleCount; k > 0; --k)
1184 {
1185 buffer[static_cast<size_t>(x)] = previous;
1186 x += deltaX;
1187 }
1188 rleCount = 0;
1189 rleCountByte = 0;
1190 }
1191 buffer[static_cast<size_t>(x)] = rgbe;
1192 x += deltaX;
1193 }
1194 }
1195 previous = rgbe;
1196 }
1197
1198 // decode rgbe information
1199 //
1200 TPixel* scanline = &raster_[static_cast<size_t>(y) * header.width];
1201 for (size_t x = 0; x != header.width; ++x)
1202 {
1203 const impl::Bytes4 rgbe = buffer[x];
1204 TPixel& pixel = scanline[x];
1205 const float exponent = exponents[rgbe[3]];
1206 for (size_t k = 0; k < 3; ++k)
1207 {
1208 pixel[k] = static_cast<float>(rgbe[k]) * inverseCorrections[k] * exponent;
1209 }
1210 }
1211 }
1212 return stream;
1213}
1214
1215
1216
1217BinaryIStream& Image::openPfm(BinaryIStream& stream)
1218{
1219 HeaderPfm header;
1220 header.readFrom(stream);
1221 if (!stream)
1222 {
1223 LASS_THROW_EX(BadFormat, "not a PFM file.");
1224 }
1225
1226 colorSpace_ = defaultColorSpace();
1227 colorSpace_.gamma = 1;
1228
1229 resize(header.height, header.width);
1230 EndiannessSetter(stream, header.endianness);
1231
1232 for (size_t y = rows_; y > 0; --y)
1233 {
1234 TPixel* scanline = &raster_[(y - 1) * cols_];
1235 if (header.isGrey)
1236 {
1237 for (size_t x = 0; x < cols_; ++x)
1238 {
1239 num::Tfloat32 g;
1240 stream >> g;
1241 scanline[x] = TPixel(g, g, g, 1);
1242 }
1243 }
1244 else
1245 {
1246 for (size_t x = 0; x < cols_; ++x)
1247 {
1248 num::Tfloat32 r, g, b;
1249 stream >> r >> g >> b;
1250 scanline[x] = TPixel(r, g, b, 1);
1251 }
1252 }
1253 }
1254
1255 return stream;
1256}
1257
1258
1259
1260BinaryIStream& Image::openIgi(BinaryIStream& stream)
1261{
1262 HeaderIgi header;
1263 header.readFrom(stream);
1264
1265 if (!stream || header.magic != magicIgi_ || header.version < 1 || header.version > 2)
1266 {
1267 LASS_THROW_EX(BadFormat, "not an IGI version <= 2 file.");
1268 }
1269 if (header.zipped)
1270 {
1271 LASS_THROW_EX(BadFormat, "cannot read zipped IGI files.");
1272 }
1273 if (header.dataSize < 0 || static_cast<num::Tuint32>(header.dataSize) != 12 * header.width * header.height)
1274 {
1275 LASS_THROW_EX(BadFormat, "error in dataSize field.");
1276 }
1277
1278 // The current file format does not specify chromaticies, only distinguishes
1279 // between RGB and XYZ. Use default RGB space in case of the former.
1280 colorSpace_ = header.rgb ? defaultColorSpace() : xyzColorSpace();
1281 colorSpace_.gamma = 1;
1282 colorSpace_.isFromFile = true;
1283
1284 resize(header.height, header.width);
1285 num::Tfloat32 r, g, b;
1286 EndiannessSetter(stream, num::littleEndian);
1287 for (TRaster::iterator i = raster_.begin(); i != raster_.end(); ++i)
1288 {
1289 stream >> r >> g >> b;
1290 i->r = r;
1291 i->g = g;
1292 i->b = b;
1293 i->a = 1;
1294 }
1295
1296 return stream;
1297}
1298
1299
1300BinaryOStream& Image::saveLass(BinaryOStream& stream) const
1301{
1302 HeaderLass header;
1303 header.lass = magicLass_;
1304 header.version = 2;
1305 header.rows = num::numCast<num::Tuint32>(rows_);
1306 header.cols = num::numCast<num::Tuint32>(cols_);
1307 header.writeTo(stream);
1308
1309 EndiannessSetter(stream, num::littleEndian);
1310
1311 for (size_t i = 0; i < numChromaticities; ++i)
1312 {
1313 const num::Tfloat32 x = colorSpace_[i].x;
1314 const num::Tfloat32 y = colorSpace_[i].y;
1315 stream << x << y;
1316 }
1317
1318 const num::Tfloat32 c = colorSpace_.gamma;
1319 stream << c;
1320
1321 for (TRaster::const_iterator i = raster_.begin(); i != raster_.end(); ++i)
1322 {
1323 const num::Tfloat32 r = i->r;
1324 const num::Tfloat32 g = i->g;
1325 const num::Tfloat32 b = i->b;
1326 const num::Tfloat32 a = i->a;
1327 stream << r << g << b << a;
1328 }
1329
1330 return stream;
1331}
1332
1333
1334/** Save TARGA file (type 2, 32 bit).
1335 */
1336BinaryOStream& Image::saveTarga(BinaryOStream& stream) const
1337{
1338 const TValue scale(255);
1339 const TValue zero(0);
1340 const TValue one(1);
1341
1342 // STEP 1: Make a header of the right type
1343 HeaderTarga header;
1344 header.idLength = 0;
1345 header.colorMapType = 0;
1346 header.imageType = 10; // rle true color
1347 header.colorMapOrigin = 0;
1348 header.colorMapLength = 0;
1349 header.colorMapEntrySize = 0;
1350 header.imageXorigin = 0;
1351 header.imageYorigin = 0;
1352 header.imageWidth = num::numCast<num::Tuint16>(cols_);;
1353 header.imageHeight = num::numCast<num::Tuint16>(rows_);
1354 header.imagePixelSize = 32;
1355 header.imageDescriptor = 0x08; // 8 attribute bits
1356
1357 header.writeTo(stream);
1358
1359 std::vector<impl::Bytes4> buffer(header.imageWidth);
1360 std::vector<impl::Bytes4> rleBuffer(128);
1361 for (size_t y = rows_; y > 0; --y)
1362 {
1363 size_t x;
1364
1365 // encode in scanline buffer
1366 //
1367 const TPixel* scanline = &raster_[(y - 1) * cols_];
1368 for (x = 0; x < cols_; ++x)
1369 {
1370 const TPixel& pixel = scanline[x];
1371 impl::Bytes4& bytes = buffer[x];
1372 bytes[0] = static_cast<num::Tuint8>(num::clamp(pixel.b, zero, one) * scale);
1373 bytes[1] = static_cast<num::Tuint8>(num::clamp(pixel.g, zero, one) * scale);
1374 bytes[2] = static_cast<num::Tuint8>(num::clamp(pixel.r, zero, one) * scale);
1375 bytes[3] = static_cast<num::Tuint8>(num::clamp(pixel.a, zero, one) * scale);
1376 }
1377
1378 // run-length encode buffer
1379 //
1380 size_t LASS_UNUSED(totalLength) = 0;
1381 num::Tuint8 numDiff = 0;
1382 x = 0;
1383 while (x < cols_)
1384 {
1385 const impl::Bytes4& bytes = buffer[x];
1386 size_t x2 = x;
1387 num::Tuint8 numSame = 0;
1388 while (x2 < cols_ && numSame < 128 && buffer[x2] == bytes)
1389 {
1390 ++x2;
1391 ++numSame;
1392 }
1393 if (numSame == 1)
1394 {
1395 rleBuffer[numDiff] = bytes;
1396 ++numDiff;
1397 ++x;
1398 }
1399 if (numDiff == 128 || ((numSame > 1 || x == cols_) && numDiff > 0))
1400 {
1401 stream << static_cast<num::Tuint8>(numDiff - 1);
1402 for (num::Tuint8 i = 0; i < numDiff; ++i)
1403 {
1404 stream.write(&rleBuffer[i], 4);
1405 }
1406 totalLength += numDiff;
1407 LASS_ASSERT(totalLength == x);
1408 numDiff = 0;
1409 }
1410 if (numSame > 1)
1411 {
1412 stream << static_cast<num::Tuint8>((numSame - 1) | 0x80);
1413 stream.write(bytes.get(), 4);
1414 x = x2;
1415 totalLength += numSame;
1416 LASS_ASSERT(totalLength == x);
1417 numSame = 0;
1418 }
1419 }
1420 LASS_ASSERT(totalLength == cols_);
1421 }
1422
1423 return stream;
1424}
1425
1426
1427/** Save RADIANCE HDR
1428 */
1429BinaryOStream& Image::saveRadianceHdr(BinaryOStream& stream) const
1430{
1431 if (cols_ > 0x7fff)
1432 {
1433 LASS_THROW_EX(BadFormat, "cannot save this as RADIANCE HDR, because image is too wide");
1434 }
1435 HeaderRadianceHdr header;
1436 header.height = rows_;
1437 header.width = cols_;
1438 header.yIncreasing = false;
1439 header.xIncreasing = true;
1440 header.isRgb = colorSpace_ != xyzColorSpace();
1441 for (size_t i = 0; i < numChromaticities; ++i)
1442 {
1443 header.primaries[2 * i] = colorSpace_[i].x;
1444 header.primaries[2 * i + 1] = colorSpace_[i].y;
1445 }
1446 header.writeTo(stream);
1447
1448 std::vector<impl::Bytes4> buffer(cols_);
1449 std::vector<num::Tuint8> rleBuffer(128);
1450
1451 for (size_t y = 0; y < rows_; ++y)
1452 {
1453 // first, transform a line of rgb pixels to rgbe reprentation
1454 //
1455 const TPixel* line = &raster_[y * cols_];
1456 for (size_t x = 0; x < cols_; ++x)
1457 {
1458 const TPixel& pixel = line[x];
1459 impl::Bytes4& rgbe = buffer[x];
1460 const float maximum = std::max(std::max(pixel.r, pixel.g), pixel.b);
1461 if (maximum < 1e-32)
1462 {
1463 rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
1464 }
1465 else
1466 {
1467 int exponent;
1468 const float mantissa = ::frexpf(maximum, &exponent);
1469 for (size_t k = 0; k < 3; ++k)
1470 {
1471 const float normalized = pixel[k] * (256.f * mantissa / maximum);
1472 rgbe[k] = static_cast<num::Tuint8>(num::clamp(normalized, 0.f, 255.f));
1473 }
1474 rgbe[3] = static_cast<num::Tuint8>(num::clamp(exponent + 128, 0, 255));
1475 }
1476 }
1477
1478 // rle encode each channel
1479 //
1480 num::Tuint8 bytes[4];
1481 bytes[0] = 2;
1482 bytes[1] = 2;
1483 bytes[2] = static_cast<num::Tuint8>((cols_ & 0x7f00) >> 8);
1484 bytes[3] = static_cast<num::Tuint8>(cols_ & 0xff);
1485 stream.write(bytes, 4);
1486
1487 for (size_t k = 0; k < 4; ++k)
1488 {
1489 num::Tuint8 numDiff = 0;
1490 size_t x = 0;
1491 while (x < cols_)
1492 {
1493 num::Tuint8 value = buffer[x][k];
1494 size_t x2 = x;
1495 num::Tuint8 numSame = 0;
1496 while (x2 < cols_ && numSame < 127 && buffer[x2][k] == value)
1497 {
1498 ++x2;
1499 ++numSame;
1500 }
1501 if (numSame > 2)
1502 {
1503 if (numDiff > 0)
1504 {
1505 stream << numDiff;
1506 stream.write(&rleBuffer[0], numDiff);
1507 numDiff = 0;
1508 }
1509
1510 const num::Tuint8 spanField = static_cast<num::Tuint8>(numSame | 0x80);
1511 stream << spanField << value;
1512 x = x2;
1513 }
1514 else
1515 {
1516 if (numDiff == 128)
1517 {
1518 stream << numDiff;
1519 stream.write(&rleBuffer[0], numDiff);
1520 numDiff = 0;
1521 }
1522 rleBuffer[numDiff] = value;
1523 ++numDiff;
1524 ++x;
1525 }
1526 }
1527 if (numDiff > 0)
1528 {
1529 stream << numDiff;
1530 stream.write(&rleBuffer[0], numDiff);
1531 }
1532 }
1533 }
1534 return stream;
1535}
1536
1537
1538
1539BinaryOStream& Image::savePfm(BinaryOStream& stream) const
1540{
1541 HeaderPfm header;
1542 header.height = rows_;
1543 header.width = cols_;
1544 header.writeTo(stream);
1545
1546 EndiannessSetter(stream, header.endianness);
1547
1548 for (size_t y = rows_; y > 0; --y)
1549 {
1550 const TPixel* scanline = &raster_[(y - 1) * cols_];
1551 for (size_t x = 0; x < cols_; ++x)
1552 {
1553 const TPixel& p = scanline[x];
1554 const num::Tfloat32 r = p.r;
1555 const num::Tfloat32 g = p.g;
1556 const num::Tfloat32 b = p.b;
1557 stream << r << g << b;
1558 }
1559 }
1560
1561 return stream;
1562}
1563
1564
1565
1566BinaryOStream& Image::saveIgi(BinaryOStream& stream) const
1567{
1568 HeaderIgi header;
1569 header.magic = magicIgi_;
1570 header.version = 1;
1571 header.numSamples = static_cast<num::Tfloat64>(rows_ * cols_);
1572 header.width = num::numCast<num::Tuint32>(cols_);
1573 header.height = num::numCast<num::Tuint32>(rows_);
1574 header.superSampling = 1;
1575 header.zipped = 0;
1576 header.dataSize = num::numCast<num::Tint32>(12 * rows_ * cols_);
1577 header.rgb = colorSpace_ == xyzColorSpace() ? 0 : 1;
1578 header.writeTo(stream);
1579
1580 EndiannessSetter(stream, num::littleEndian);
1581
1582 for (TRaster::const_iterator i = raster_.begin(); i != raster_.end(); ++i)
1583 {
1584 const num::Tfloat32 r = i->r;
1585 const num::Tfloat32 g = i->g;
1586 const num::Tfloat32 b = i->b;
1587 stream << r << g << b;
1588 }
1589
1590 return stream;
1591}
1592
1593
1594
1595Image::FileFormat Image::findFormat(const std::string& formatTag)
1596{
1597 const std::string key = stde::tolower(formatTag);
1598 TFileFormats::const_iterator i = fileFormats_.find(key);
1599 if (i == fileFormats_.end())
1600 {
1601 LASS_THROW_EX(BadFormat, "unknown fileformat '" << key << "'.");
1602 }
1603 return i->second;
1604}
1605
1606
1607
1608BinaryIStream& Image::readLine(BinaryIStream& stream, std::string& line)
1609{
1610 std::string result;
1611 while (true)
1612 {
1613 char character;
1614 stream >> character;
1615 if (!stream.good())
1616 {
1617 return stream;
1618 }
1619 if (character == '\n')
1620 {
1621 line.swap(result);
1622 return stream;
1623 }
1624 result += character;
1625 }
1626}
1627
1628
1629
1630BinaryOStream& Image::writeLine(BinaryOStream& stream, const std::string& iString)
1631{
1632 stream.write(iString.data(), iString.size());
1633 stream << '\n';
1634 return stream;
1635}
1636
1637
1638
1639Image::TFileFormats Image::fillFileFormats()
1640{
1641 TFileFormats formats;
1642 formats["lass"] = FileFormat(&Image::openLass, &Image::saveLass);
1643 formats["targa"] = FileFormat(&Image::openTarga, &Image::saveTarga);
1644 formats["tga"] = formats["targa"];
1645 formats["hdr"] = FileFormat(&Image::openRadianceHdr, &Image::saveRadianceHdr);
1646 formats["pic"] = formats["hdr"];
1647 formats["rgbe"] = formats["hdr"];
1648 formats["pfm"] = FileFormat(&Image::openPfm, &Image::savePfm);
1649 formats["igi"] = FileFormat(&Image::openIgi, &Image::saveIgi);
1650 return formats;
1651}
1652
1653
1654
1655/** return sRGB as colorSpace
1656 */
1657const Image::ColorSpace Image::defaultColorSpace()
1658{
1659 ColorSpace result;
1660 result.red = TChromaticity(0.6400f, 0.3300f);
1661 result.green = TChromaticity(0.3000f, 0.6000f);
1662 result.blue = TChromaticity(0.1500f, 0.0600f);
1663 result.white = TChromaticity(0.3127f, 0.3290f);
1664 result.gamma = 1;
1665 result.isFromFile = false;
1666 return result;
1667}
1668
1669
1670
1671const Image::ColorSpace Image::xyzColorSpace()
1672{
1673 ColorSpace result;
1674 result.red = TChromaticity(1.f, 0.f);
1675 result.green = TChromaticity(0.f, 1.f);
1676 result.blue = TChromaticity(0.f, 0.f);
1677 result.white = TChromaticity(1.f / 3, 1.f / 3);
1678 result.gamma = 1;
1679 result.isFromFile = false;
1680 return result;
1681}
1682
1683
1684
1685// --- HeaderLass -----------------------------------------------------------------------------------
1686
1687void Image::HeaderLass::readFrom(BinaryIStream& stream)
1688{
1689 EndiannessSetter(stream, num::littleEndian);
1690 stream >> lass >> version >> rows >> cols;
1691}
1692
1693
1694
1695void Image::HeaderLass::writeTo(BinaryOStream& stream)
1696{
1697 EndiannessSetter(stream, num::littleEndian);
1698 stream << lass << version << rows << cols;
1699}
1700
1701
1702
1703// --- HeaderTarga ---------------------------------------------------------------------------------
1704
1705void Image::HeaderTarga::readFrom(BinaryIStream& stream)
1706{
1707 EndiannessSetter(stream, num::littleEndian);
1708 stream >> idLength >> colorMapType >> imageType >> colorMapOrigin >> colorMapLength >>
1709 colorMapEntrySize >> imageXorigin >> imageYorigin >> imageWidth >> imageHeight >>
1710 imagePixelSize >> imageDescriptor;
1711}
1712
1713
1714
1715void Image::HeaderTarga::writeTo(BinaryOStream& stream)
1716{
1717 EndiannessSetter(stream, num::littleEndian);
1718 stream << idLength << colorMapType << imageType << colorMapOrigin << colorMapLength <<
1719 colorMapEntrySize << imageXorigin << imageYorigin << imageWidth << imageHeight <<
1720 imagePixelSize << imageDescriptor;
1721}
1722
1723
1724
1725// --- HeaderRadianceHdr ---------------------------------------------------------------------------
1726
1727Image::HeaderRadianceHdr::HeaderRadianceHdr():
1728 exposure(1.f),
1729 height(0),
1730 width(0),
1731 yIncreasing(false),
1732 xIncreasing(true),
1733 isRgb(true),
1734 isDefaultPrimaries(true)
1735{
1736 std::fill_n(colorCorr, static_cast<int>(sizeColorCorr), 1.f);
1737 const float defaultPrimaries[sizePrimaries] =
1738 { 0.640f, 0.330f, 0.290f, 0.600f, 0.150f, 0.060f, 0.333f, 0.333f };
1739 stde::copy_n(defaultPrimaries, static_cast<int>(sizePrimaries), primaries);
1740}
1741
1742void Image::HeaderRadianceHdr::readFrom(BinaryIStream& stream)
1743{
1744 std::size_t magicSize = magicRadiance_.size();
1745 std::vector<char> magic(magicSize);
1746 stream.read(&magic[0], magicSize);
1747 if (!stream.good() || !stde::equal_r(magicRadiance_, magic))
1748 {
1749 LASS_THROW_EX(BadFormat, "file is not of RADIANCE file format");
1750 }
1751
1752 // first, read the real header
1753 //
1754 while (true)
1755 {
1756 std::string line;
1757 readLine(stream, line);
1758 if (!stream.good())
1759 {
1760 LASS_THROW_EX(BadFormat, "syntax error in header of RADIANCE HDR file");
1761 }
1762 if (line.empty())
1763 {
1764 break;
1765 }
1766 if (stde::begins_with(line, std::string("#")))
1767 {
1768 continue;
1769 }
1770 std::vector<std::string> splitted = stde::split(line, std::string("="), 1);
1771 if (splitted.size() != 2)
1772 {
1773 LASS_THROW_EX(BadFormat, "syntax error in header of RADIANCE HDR file");
1774 }
1775 std::string command = stde::toupper(splitted[0]);
1776 std::string value = stde::strip(splitted[1]);
1777 if (command == "FORMAT")
1778 {
1779 if (value == "32-bit_rle_rgbe")
1780 {
1781 isRgb = true;
1782 }
1783 else if (value == "32-bit_rle_xyze")
1784 {
1785 isRgb = false;
1786 }
1787 else
1788 {
1789 LASS_THROW_EX(BadFormat, "unknown value for FORMAT field of header of RADIANCE HDR file");
1790 }
1791 }
1792 else if (command == "EXPOSURE")
1793 {
1794 exposure *= util::stringCast<float>(value);
1795 }
1796 else if (command == "COLORCORR")
1797 {
1798 std::vector<std::string> values = stde::split(value);
1799 if (values.size() != sizeColorCorr)
1800 {
1801 LASS_THROW_EX(BadFormat, "syntax error in COLORCORR field of header of RADIANCE HDR file");
1802 }
1803 stde::transform_r(values, colorCorr, util::stringCast<float, std::string>);
1804 }
1805 else if (command == "PRIMARIES")
1806 {
1807 std::vector<std::string> values = stde::split(value);
1808 if (values.size() != sizePrimaries)
1809 {
1810 LASS_THROW_EX(BadFormat, "syntax error in PRIMARIES field of header of RADIANCE HDR file");
1811 }
1812 stde::transform_r(values, primaries, util::stringCast<float, std::string>);
1813 isDefaultPrimaries = false;
1814 }
1815 }
1816
1817 // second, interpret the resolution line
1818 std::string line;
1819 readLine(stream, line);
1820 if (!stream.good())
1821 {
1822 LASS_THROW_EX(BadFormat, "syntax error in resolution line of RADIANCE HDR file");
1823 }
1824 std::vector<std::string> splitted = stde::split(line);
1825 if (splitted.size() != 4)
1826 {
1827 LASS_THROW_EX(BadFormat, "resolution line of RADIANCE HDR file not of 4 elements");
1828 }
1829 std::string y = stde::toupper(splitted[0]);
1830 std::string x = stde::toupper(splitted[2]);
1831 if (!(y == "+Y" || y == "-Y") || !(x == "+X" || x == "-X"))
1832 {
1833 LASS_THROW_EX(BadFormat, "syntax error in resolution line of RADIANCE HDR file");
1834 }
1835 yIncreasing = y == "+Y";
1836 xIncreasing = x == "+X";
1837 height = util::stringCast<size_t>(splitted[1]);
1838 width = util::stringCast<size_t>(splitted[3]);
1839}
1840
1841
1842
1843void Image::HeaderRadianceHdr::writeTo(BinaryOStream& stream)
1844{
1845 stream.write(magicRadiance_.data(), magicRadiance_.length());
1846 writeLine(stream, "SOFTWARE=LASS, http://lass.sourceforge.net");
1847 writeLine(stream, std::string("FORMAT=32-bit_rle_") + (isRgb ? "rgbe" : "xyze"));
1848 writeLine(stream, "EXPOSURE=" + util::stringCast<std::string>(exposure));
1849 writeLine(stream, std::string("COLORCORR=") +
1850 stde::join(std::string(" "), colorCorr, colorCorr + sizeColorCorr));
1851 writeLine(stream, std::string("PRIMARIES=") +
1852 stde::join(std::string(" "), primaries, primaries + sizePrimaries));
1853 writeLine(stream, "");
1854 std::ostringstream resolution;
1855 resolution << (yIncreasing ? "+Y" : "-Y") << " " << height;
1856 resolution << " " << (xIncreasing ? "+X" : "-X") << " " << width;
1857 writeLine(stream, resolution.str());
1858}
1859
1860
1861
1862// --- HeaderPfm -----------------------------------------------------------------------------------
1863
1864Image::HeaderPfm::HeaderPfm():
1865 width(0),
1866 height(0),
1867 aspect(1),
1868 endianness(num::littleEndian),
1869 isGrey(false)
1870{
1871}
1872
1873
1874
1875void Image::HeaderPfm::readFrom(BinaryIStream& stream)
1876{
1877 const std::string magicPF = "PF";
1878 const std::string magicPf = "Pf";
1879 LASS_ASSERT(magicPF.length() == magicPf.length());
1880 const size_t magicSize = magicPF.length();
1881 std::vector<char> magic(magicSize);
1882 stream.read(&magic[0], magicSize);
1883 if (!stream.good() || !(stde::equal_r(magicPF, magic) || stde::equal_r(magicPf, magic)))
1884 {
1885 LASS_THROW_EX(BadFormat, "file is not a PFM file");
1886 }
1887
1888 isGrey = stde::equal_r(magicPf, magic);
1889
1890 // we need to read a number of attributes that may be spread across one or more lines
1891 //
1892 const size_t numAttributes = 3;
1893 std::string attributes[numAttributes];
1894 size_t k = 0;
1895 std::string line;
1896 std::string attr;
1897 while (k < numAttributes)
1898 {
1899 Image::readLine(stream, line);
1900 if (!stream.good())
1901 {
1902 LASS_THROW_EX(BadFormat, "syntax error in header of PFM file");
1903 }
1904 const size_t startComments = line.find("#");
1905 std::istringstream buffer(line.substr(0, startComments));
1906 while (k < numAttributes && buffer.good())
1907 {
1908 if (buffer >> attr)
1909 {
1910 attributes[k++] = attr;
1911 }
1912 }
1913 }
1914
1915 try
1916 {
1917 width = util::stringCast<size_t>(attributes[0]);
1918 height = util::stringCast<size_t>(attributes[1]);
1919 aspect = util::stringCast<float>(attributes[2]);
1920 }
1921 catch (const util::BadStringCast&)
1922 {
1923 LASS_THROW_EX(BadFormat, "syntax error in header of PFM file");
1924 }
1925
1926 endianness = aspect < 0 ? num::littleEndian : num::bigEndian;
1927 aspect = num::abs(aspect);
1928}
1929
1930
1931
1932void Image::HeaderPfm::writeTo(BinaryOStream& stream)
1933{
1934 std::ostringstream buffer;
1935 buffer << "PF\n" << width << " " << height << "\n" << (endianness == num::littleEndian ? -aspect : aspect);
1936 writeLine(stream, buffer.str());
1937}
1938
1939
1940
1941// --- HeaderIgi -----------------------------------------------------------------------------------
1942
1943void Image::HeaderIgi::readFrom(BinaryIStream& stream)
1944{
1945 EndiannessSetter(stream, num::littleEndian);
1946 stream >> magic >> version >> numSamples >> width >> height >> superSampling >> zipped
1947 >> dataSize >> rgb;
1948 stream.seekg(padding, std::ios_base::cur);
1949}
1950
1951
1952
1953void Image::HeaderIgi::writeTo(BinaryOStream& stream)
1954{
1955 EndiannessSetter(stream, num::littleEndian);
1956 stream << magic << version << numSamples << width << height << superSampling << zipped
1957 << dataSize << rgb;
1958 stream.seekp(padding, std::ios_base::cur);
1959}
1960
1961// --- free ----------------------------------------------------------------------------------------
1962
1963
1964
1965}
1966
1967}
1968
1969// EOF
Input Stream for binary files.
base class of binary input streams.
BinaryOStream to file.
base class of binary output streams.
size_t write(const void *buffer, size_t byteLength)
write a buffer of bytes to the stream
image thing?
Definition image.h:77
void rout(const Image &other)
this = other out this
Definition image.cpp:697
void filterInverseExposure(TParam exposureTime)
apply exposure to image
Definition image.cpp:873
void filterMedian(size_t boxSize)
Apply a median filter on image.
Definition image.cpp:760
const TPixel * data() const
Return const data block.
Definition image.cpp:486
void transformColors(const ColorSpace &newColorSpace)
Transform the colors from the current color spacer to destColorSpace.
Definition image.cpp:538
void atop(const Image &other)
this = this atop other
Definition image.cpp:657
void swap(Image &other)
swap two images
Definition image.cpp:424
size_t cols() const
Return width of image.
Definition image.cpp:609
void clampNegatives()
clamp all negative pixel components to zero.
Definition image.cpp:737
void ratop(const Image &other)
this = other atop this
Definition image.cpp:707
void out(const Image &other)
this = this out other
Definition image.cpp:647
void filterGamma(TParam gammaExponent)
apply gamma correction to image
Definition image.cpp:846
void through(const Image &other)
this = this through other
Definition image.cpp:667
size_t rows() const
Return height of image.
Definition image.cpp:600
const TPixel & operator()(size_t row, size_t col) const
Return const pixel at position row, col.
Definition image.cpp:438
bool isEmpty() const
Return true if image is empty (no data)
Definition image.cpp:618
Image()
Default constructor.
Definition image.cpp:98
void open(const std::string &path)
Open image from file.
Definition image.cpp:262
void rthrough(const Image &other)
this = other through this
Definition image.cpp:717
void plus(const Image &other)
this = this plus other = other plus this
Definition image.cpp:727
void rover(const Image &other)
this = other over this
Definition image.cpp:677
void save(const std::string &path)
Save image to file.
Definition image.cpp:305
~Image()
Destructor.
Definition image.cpp:184
void filterExposure(TParam exposureTime)
apply exposure to image
Definition image.cpp:860
void in(const Image &other)
this = this in other
Definition image.cpp:637
void reset()
Reset image to empty image.
Definition image.cpp:194
void over(const Image &other)
this = this over other
Definition image.cpp:627
Image & operator=(const Image &other)
Copy other into this image.
Definition image.cpp:413
void rin(const Image &other)
this = other in this
Definition image.cpp:687
const TPixel & at(TSignedSize row, TSignedSize col) const
Return const pixel at position row, col.
Definition image.cpp:462
const ColorSpace & colorSpace() const
Return colorSpace of image data.
Definition image.cpp:504
a linear 3D transformation
T inv(const T &x)
return x ^ -1
Definition basic_ops.h:178
const T & clamp(const T &x, const T &min, const T &max)
if x < min return min, else if x > max return max, else return x.
Definition basic_ops.h:226
T abs(const T &x)
if x < 0 return -x, else return x.
Definition basic_ops.h:145
std::string fileExtension(const std::string &fileName)
return the part of the file name behind the last dot.
OutputIterator copy_n(InputIterator first, Size count, OutputIterator result)
copy count elements from sequence starting at first to sequence starting at result
bool begins_with(const std::basic_string< Char, Traits, Alloc > &input, const std::basic_string< Char, Traits, Alloc > &prefix)
returns true if input begins with the input prefix
std::basic_string< Char, Traits, Alloc > tolower(const std::basic_string< Char, Traits, Alloc > &input, const std::locale &locale=std::locale())
convert std::basic_string to lower case by using user locale
std::vector< std::basic_string< Char, Traits, Alloc > > split(const std::basic_string< Char, Traits, Alloc > &to_be_split)
Reflects the Python function split without seperator argument.
std::basic_string< Char, Traits, Alloc > toupper(const std::basic_string< Char, Traits, Alloc > &input, const std::locale &locale=std::locale())
convert std::basic_string to upper case by using user locale
std::basic_string< Char, Traits, Alloc > strip(const std::basic_string< Char, Traits, Alloc > &to_be_stripped, const std::basic_string< Char, Traits, Alloc > &to_be_removed)
Return a copy of the string to_be_stripped with both leading and trailing characters removed.
bool equal_r(const InputRange1 &range1, const InputRange2 &range2)
std::mismatch wrapper for ranges
OutputIterator transform_r(const InputRange &range, OutputIterator result, UnaryOperation op)
std::transform wrapper for ranges
streams, binary streams, vrmlstreams, ...
ColorRGBA plus(const ColorRGBA &a, const ColorRGBA &b)
ColorRGBA over(const ColorRGBA &a, const ColorRGBA &b)
placement of foreground a in front of background b.
ColorRGBA out(const ColorRGBA &a, const ColorRGBA &b)
a held out by b, part of a outside b.
ColorRGBA through(const ColorRGBA &a, const ColorRGBA &b)
a seen through color filter b.
ColorRGBA in(const ColorRGBA &a, const ColorRGBA &b)
part of a inside b.
ColorRGBA atop(const ColorRGBA &a, const ColorRGBA &b)
union of a in b and b out a.
Library for Assembled Shared Sources.
Definition config.h:53