Mercurial > hg > orthanc
comparison OrthancFramework/Sources/Images/ImageProcessing.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Core/Images/ImageProcessing.cpp@2a170a8f1faf |
children | 55727d85f419 |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeaders.h" | |
35 #include "ImageProcessing.h" | |
36 | |
37 #include "Image.h" | |
38 #include "ImageTraits.h" | |
39 #include "PixelTraits.h" | |
40 #include "../OrthancException.h" | |
41 | |
42 #ifdef __EMSCRIPTEN__ | |
43 /* | |
44 Avoid this error: | |
45 ----------------- | |
46 .../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion] | |
47 .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:333:28: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here | |
48 .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:1006:9: note: in instantiation of function template specialization 'Orthanc::MultiplyConstantInternal<unsigned char, true>' requested here | |
49 */ | |
50 #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" | |
51 #endif | |
52 | |
53 #include <boost/math/special_functions/round.hpp> | |
54 | |
55 #include <cassert> | |
56 #include <string.h> | |
57 #include <limits> | |
58 #include <stdint.h> | |
59 #include <algorithm> | |
60 | |
61 namespace Orthanc | |
62 { | |
63 double ImageProcessing::ImagePoint::GetDistanceTo(const ImagePoint& other) const | |
64 { | |
65 double dx = (double)(other.GetX() - GetX()); | |
66 double dy = (double)(other.GetY() - GetY()); | |
67 return sqrt(dx * dx + dy * dy); | |
68 } | |
69 | |
70 double ImageProcessing::ImagePoint::GetDistanceToLine(double a, double b, double c) const // where ax + by + c = 0 is the equation of the line | |
71 { | |
72 return std::abs(a * static_cast<double>(GetX()) + b * static_cast<double>(GetY()) + c) / pow(a * a + b * b, 0.5); | |
73 } | |
74 | |
75 template <typename TargetType, typename SourceType> | |
76 static void ConvertInternal(ImageAccessor& target, | |
77 const ImageAccessor& source) | |
78 { | |
79 // WARNING - "::min()" should be replaced by "::lowest()" if | |
80 // dealing with float or double (which is not the case so far) | |
81 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" | |
82 const TargetType minValue = std::numeric_limits<TargetType>::min(); | |
83 const TargetType maxValue = std::numeric_limits<TargetType>::max(); | |
84 | |
85 const unsigned int width = source.GetWidth(); | |
86 const unsigned int height = source.GetHeight(); | |
87 | |
88 for (unsigned int y = 0; y < height; y++) | |
89 { | |
90 TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
91 const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); | |
92 | |
93 for (unsigned int x = 0; x < width; x++, t++, s++) | |
94 { | |
95 if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue)) | |
96 { | |
97 *t = minValue; | |
98 } | |
99 else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue)) | |
100 { | |
101 *t = maxValue; | |
102 } | |
103 else | |
104 { | |
105 *t = static_cast<TargetType>(*s); | |
106 } | |
107 } | |
108 } | |
109 } | |
110 | |
111 | |
112 template <typename SourceType> | |
113 static void ConvertGrayscaleToFloat(ImageAccessor& target, | |
114 const ImageAccessor& source) | |
115 { | |
116 assert(sizeof(float) == 4); | |
117 | |
118 const unsigned int width = source.GetWidth(); | |
119 const unsigned int height = source.GetHeight(); | |
120 | |
121 for (unsigned int y = 0; y < height; y++) | |
122 { | |
123 float* t = reinterpret_cast<float*>(target.GetRow(y)); | |
124 const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); | |
125 | |
126 for (unsigned int x = 0; x < width; x++, t++, s++) | |
127 { | |
128 *t = static_cast<float>(*s); | |
129 } | |
130 } | |
131 } | |
132 | |
133 | |
134 template <PixelFormat TargetFormat> | |
135 static void ConvertFloatToGrayscale(ImageAccessor& target, | |
136 const ImageAccessor& source) | |
137 { | |
138 typedef typename PixelTraits<TargetFormat>::PixelType TargetType; | |
139 | |
140 assert(sizeof(float) == 4); | |
141 | |
142 const unsigned int width = source.GetWidth(); | |
143 const unsigned int height = source.GetHeight(); | |
144 | |
145 for (unsigned int y = 0; y < height; y++) | |
146 { | |
147 TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
148 const float* p = reinterpret_cast<const float*>(source.GetConstRow(y)); | |
149 | |
150 for (unsigned int x = 0; x < width; x++, p++, q++) | |
151 { | |
152 PixelTraits<TargetFormat>::FloatToPixel(*q, *p); | |
153 } | |
154 } | |
155 } | |
156 | |
157 | |
158 template <typename TargetType> | |
159 static void ConvertColorToGrayscale(ImageAccessor& target, | |
160 const ImageAccessor& source) | |
161 { | |
162 assert(source.GetFormat() == PixelFormat_RGB24); | |
163 | |
164 // WARNING - "::min()" should be replaced by "::lowest()" if | |
165 // dealing with float or double (which is not the case so far) | |
166 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" | |
167 const TargetType minValue = std::numeric_limits<TargetType>::min(); | |
168 const TargetType maxValue = std::numeric_limits<TargetType>::max(); | |
169 | |
170 const unsigned int width = source.GetWidth(); | |
171 const unsigned int height = source.GetHeight(); | |
172 | |
173 for (unsigned int y = 0; y < height; y++) | |
174 { | |
175 TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
176 const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
177 | |
178 for (unsigned int x = 0; x < width; x++, t++, s += 3) | |
179 { | |
180 // Y = 0.2126 R + 0.7152 G + 0.0722 B | |
181 int32_t v = (2126 * static_cast<int32_t>(s[0]) + | |
182 7152 * static_cast<int32_t>(s[1]) + | |
183 0722 * static_cast<int32_t>(s[2])) / 10000; | |
184 | |
185 if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) | |
186 { | |
187 *t = minValue; | |
188 } | |
189 else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue)) | |
190 { | |
191 *t = maxValue; | |
192 } | |
193 else | |
194 { | |
195 *t = static_cast<TargetType>(v); | |
196 } | |
197 } | |
198 } | |
199 } | |
200 | |
201 | |
202 static void MemsetZeroInternal(ImageAccessor& image) | |
203 { | |
204 const unsigned int height = image.GetHeight(); | |
205 const size_t lineSize = image.GetBytesPerPixel() * image.GetWidth(); | |
206 const size_t pitch = image.GetPitch(); | |
207 | |
208 uint8_t *p = reinterpret_cast<uint8_t*>(image.GetBuffer()); | |
209 | |
210 for (unsigned int y = 0; y < height; y++) | |
211 { | |
212 memset(p, 0, lineSize); | |
213 p += pitch; | |
214 } | |
215 } | |
216 | |
217 | |
218 template <typename PixelType> | |
219 static void SetInternal(ImageAccessor& image, | |
220 int64_t constant) | |
221 { | |
222 if (constant == 0 && | |
223 (image.GetFormat() == PixelFormat_Grayscale8 || | |
224 image.GetFormat() == PixelFormat_Grayscale16 || | |
225 image.GetFormat() == PixelFormat_Grayscale32 || | |
226 image.GetFormat() == PixelFormat_Grayscale64 || | |
227 image.GetFormat() == PixelFormat_SignedGrayscale16)) | |
228 { | |
229 MemsetZeroInternal(image); | |
230 } | |
231 else | |
232 { | |
233 const unsigned int width = image.GetWidth(); | |
234 const unsigned int height = image.GetHeight(); | |
235 | |
236 for (unsigned int y = 0; y < height; y++) | |
237 { | |
238 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); | |
239 | |
240 for (unsigned int x = 0; x < width; x++, p++) | |
241 { | |
242 *p = static_cast<PixelType>(constant); | |
243 } | |
244 } | |
245 } | |
246 } | |
247 | |
248 | |
249 template <typename PixelType> | |
250 static void GetMinMaxValueInternal(PixelType& minValue, | |
251 PixelType& maxValue, | |
252 const ImageAccessor& source, | |
253 const PixelType LowestValue = std::numeric_limits<PixelType>::min()) | |
254 { | |
255 // Deal with the special case of empty image | |
256 if (source.GetWidth() == 0 || | |
257 source.GetHeight() == 0) | |
258 { | |
259 minValue = 0; | |
260 maxValue = 0; | |
261 return; | |
262 } | |
263 | |
264 minValue = std::numeric_limits<PixelType>::max(); | |
265 maxValue = LowestValue; | |
266 | |
267 const unsigned int height = source.GetHeight(); | |
268 const unsigned int width = source.GetWidth(); | |
269 | |
270 for (unsigned int y = 0; y < height; y++) | |
271 { | |
272 const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); | |
273 | |
274 for (unsigned int x = 0; x < width; x++, p++) | |
275 { | |
276 if (*p < minValue) | |
277 { | |
278 minValue = *p; | |
279 } | |
280 | |
281 if (*p > maxValue) | |
282 { | |
283 maxValue = *p; | |
284 } | |
285 } | |
286 } | |
287 } | |
288 | |
289 | |
290 | |
291 template <typename PixelType> | |
292 static void AddConstantInternal(ImageAccessor& image, | |
293 int64_t constant) | |
294 { | |
295 if (constant == 0) | |
296 { | |
297 return; | |
298 } | |
299 | |
300 // WARNING - "::min()" should be replaced by "::lowest()" if | |
301 // dealing with float or double (which is not the case so far) | |
302 assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double" | |
303 const int64_t minValue = std::numeric_limits<PixelType>::min(); | |
304 const int64_t maxValue = std::numeric_limits<PixelType>::max(); | |
305 | |
306 const unsigned int width = image.GetWidth(); | |
307 const unsigned int height = image.GetHeight(); | |
308 | |
309 for (unsigned int y = 0; y < height; y++) | |
310 { | |
311 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); | |
312 | |
313 for (unsigned int x = 0; x < width; x++, p++) | |
314 { | |
315 int64_t v = static_cast<int64_t>(*p) + constant; | |
316 | |
317 if (v > maxValue) | |
318 { | |
319 *p = std::numeric_limits<PixelType>::max(); | |
320 } | |
321 else if (v < minValue) | |
322 { | |
323 *p = std::numeric_limits<PixelType>::min(); | |
324 } | |
325 else | |
326 { | |
327 *p = static_cast<PixelType>(v); | |
328 } | |
329 } | |
330 } | |
331 } | |
332 | |
333 | |
334 | |
335 template <typename PixelType, | |
336 bool UseRound> | |
337 static void MultiplyConstantInternal(ImageAccessor& image, | |
338 float factor) | |
339 { | |
340 if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) | |
341 { | |
342 return; | |
343 } | |
344 | |
345 // WARNING - "::min()" should be replaced by "::lowest()" if | |
346 // dealing with float or double (which is not the case so far) | |
347 assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double" | |
348 const int64_t minValue = std::numeric_limits<PixelType>::min(); | |
349 const int64_t maxValue = std::numeric_limits<PixelType>::max(); | |
350 | |
351 const unsigned int width = image.GetWidth(); | |
352 const unsigned int height = image.GetHeight(); | |
353 | |
354 for (unsigned int y = 0; y < height; y++) | |
355 { | |
356 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); | |
357 | |
358 for (unsigned int x = 0; x < width; x++, p++) | |
359 { | |
360 int64_t v; | |
361 if (UseRound) | |
362 { | |
363 // The "round" operation is very costly | |
364 v = boost::math::llround(static_cast<float>(*p) * factor); | |
365 } | |
366 else | |
367 { | |
368 v = static_cast<int64_t>(static_cast<float>(*p) * factor); | |
369 } | |
370 | |
371 if (v > maxValue) | |
372 { | |
373 *p = std::numeric_limits<PixelType>::max(); | |
374 } | |
375 else if (v < minValue) | |
376 { | |
377 *p = std::numeric_limits<PixelType>::min(); | |
378 } | |
379 else | |
380 { | |
381 *p = static_cast<PixelType>(v); | |
382 } | |
383 } | |
384 } | |
385 } | |
386 | |
387 | |
388 // Computes "a * x + b" at each pixel => Note that this is not the | |
389 // same convention as in "ShiftScale()" | |
390 template <typename TargetType, | |
391 typename SourceType, | |
392 bool UseRound, | |
393 bool Invert> | |
394 static void ShiftScaleInternal(ImageAccessor& target, | |
395 const ImageAccessor& source, | |
396 float a, | |
397 float b, | |
398 const TargetType LowestValue) | |
399 // This function can be applied inplace (source == target) | |
400 { | |
401 if (source.GetWidth() != target.GetWidth() || | |
402 source.GetHeight() != target.GetHeight()) | |
403 { | |
404 throw OrthancException(ErrorCode_IncompatibleImageSize); | |
405 } | |
406 | |
407 if (&source == &target && | |
408 source.GetFormat() != target.GetFormat()) | |
409 { | |
410 throw OrthancException(ErrorCode_IncompatibleImageFormat); | |
411 } | |
412 | |
413 const TargetType minPixelValue = LowestValue; | |
414 const TargetType maxPixelValue = std::numeric_limits<TargetType>::max(); | |
415 const float minFloatValue = static_cast<float>(LowestValue); | |
416 const float maxFloatValue = static_cast<float>(maxPixelValue); | |
417 | |
418 const unsigned int height = target.GetHeight(); | |
419 const unsigned int width = target.GetWidth(); | |
420 | |
421 for (unsigned int y = 0; y < height; y++) | |
422 { | |
423 TargetType* p = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
424 const SourceType* q = reinterpret_cast<const SourceType*>(source.GetRow(y)); | |
425 | |
426 for (unsigned int x = 0; x < width; x++, p++, q++) | |
427 { | |
428 float v = a * static_cast<float>(*q) + b; | |
429 | |
430 if (v >= maxFloatValue) | |
431 { | |
432 *p = maxPixelValue; | |
433 } | |
434 else if (v <= minFloatValue) | |
435 { | |
436 *p = minPixelValue; | |
437 } | |
438 else if (UseRound) | |
439 { | |
440 // The "round" operation is very costly | |
441 *p = static_cast<TargetType>(boost::math::iround(v)); | |
442 } | |
443 else | |
444 { | |
445 *p = static_cast<TargetType>(std::floor(v)); | |
446 } | |
447 | |
448 if (Invert) | |
449 { | |
450 *p = maxPixelValue - *p; | |
451 } | |
452 } | |
453 } | |
454 } | |
455 | |
456 template <typename PixelType> | |
457 static void ShiftRightInternal(ImageAccessor& image, | |
458 unsigned int shift) | |
459 { | |
460 const unsigned int height = image.GetHeight(); | |
461 const unsigned int width = image.GetWidth(); | |
462 | |
463 for (unsigned int y = 0; y < height; y++) | |
464 { | |
465 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); | |
466 | |
467 for (unsigned int x = 0; x < width; x++, p++) | |
468 { | |
469 *p = *p >> shift; | |
470 } | |
471 } | |
472 } | |
473 | |
474 template <typename PixelType> | |
475 static void ShiftLeftInternal(ImageAccessor& image, | |
476 unsigned int shift) | |
477 { | |
478 const unsigned int height = image.GetHeight(); | |
479 const unsigned int width = image.GetWidth(); | |
480 | |
481 for (unsigned int y = 0; y < height; y++) | |
482 { | |
483 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); | |
484 | |
485 for (unsigned int x = 0; x < width; x++, p++) | |
486 { | |
487 *p = *p << shift; | |
488 } | |
489 } | |
490 } | |
491 | |
492 void ImageProcessing::Copy(ImageAccessor& target, | |
493 const ImageAccessor& source) | |
494 { | |
495 if (target.GetWidth() != source.GetWidth() || | |
496 target.GetHeight() != source.GetHeight()) | |
497 { | |
498 throw OrthancException(ErrorCode_IncompatibleImageSize); | |
499 } | |
500 | |
501 if (target.GetFormat() != source.GetFormat()) | |
502 { | |
503 throw OrthancException(ErrorCode_IncompatibleImageFormat); | |
504 } | |
505 | |
506 unsigned int lineSize = source.GetBytesPerPixel() * source.GetWidth(); | |
507 | |
508 assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); | |
509 | |
510 for (unsigned int y = 0; y < source.GetHeight(); y++) | |
511 { | |
512 memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); | |
513 } | |
514 } | |
515 | |
516 template <typename TargetType, typename SourceType> | |
517 static void ApplyWindowingInternal(ImageAccessor& target, | |
518 const ImageAccessor& source, | |
519 float windowCenter, | |
520 float windowWidth, | |
521 float rescaleSlope, | |
522 float rescaleIntercept, | |
523 bool invert) | |
524 { | |
525 assert(sizeof(SourceType) == source.GetBytesPerPixel() && | |
526 sizeof(TargetType) == target.GetBytesPerPixel()); | |
527 | |
528 // WARNING - "::min()" should be replaced by "::lowest()" if | |
529 // dealing with float or double (which is not the case so far) | |
530 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" | |
531 const TargetType minTargetValue = std::numeric_limits<TargetType>::min(); | |
532 const TargetType maxTargetValue = std::numeric_limits<TargetType>::max(); | |
533 const float maxFloatValue = static_cast<float>(maxTargetValue); | |
534 | |
535 const float windowIntercept = windowCenter - windowWidth / 2.0f; | |
536 const float windowSlope = (maxFloatValue + 1.0f) / windowWidth; | |
537 | |
538 const float a = rescaleSlope * windowSlope; | |
539 const float b = (rescaleIntercept - windowIntercept) * windowSlope; | |
540 | |
541 if (invert) | |
542 { | |
543 ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue); | |
544 } | |
545 else | |
546 { | |
547 ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue); | |
548 } | |
549 } | |
550 | |
551 void ImageProcessing::ApplyWindowing_Deprecated(ImageAccessor& target, | |
552 const ImageAccessor& source, | |
553 float windowCenter, | |
554 float windowWidth, | |
555 float rescaleSlope, | |
556 float rescaleIntercept, | |
557 bool invert) | |
558 { | |
559 if (target.GetWidth() != source.GetWidth() || | |
560 target.GetHeight() != source.GetHeight()) | |
561 { | |
562 throw OrthancException(ErrorCode_IncompatibleImageSize); | |
563 } | |
564 | |
565 switch (source.GetFormat()) | |
566 { | |
567 case Orthanc::PixelFormat_Float32: | |
568 { | |
569 switch (target.GetFormat()) | |
570 { | |
571 case Orthanc::PixelFormat_Grayscale8: | |
572 ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
573 break; | |
574 case Orthanc::PixelFormat_Grayscale16: | |
575 ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
576 break; | |
577 default: | |
578 throw OrthancException(ErrorCode_NotImplemented); | |
579 } | |
580 };break; | |
581 case Orthanc::PixelFormat_Grayscale8: | |
582 { | |
583 switch (target.GetFormat()) | |
584 { | |
585 case Orthanc::PixelFormat_Grayscale8: | |
586 ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
587 break; | |
588 case Orthanc::PixelFormat_Grayscale16: | |
589 ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
590 break; | |
591 default: | |
592 throw OrthancException(ErrorCode_NotImplemented); | |
593 } | |
594 };break; | |
595 case Orthanc::PixelFormat_Grayscale16: | |
596 { | |
597 switch (target.GetFormat()) | |
598 { | |
599 case Orthanc::PixelFormat_Grayscale8: | |
600 ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
601 break; | |
602 case Orthanc::PixelFormat_Grayscale16: | |
603 ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); | |
604 break; | |
605 default: | |
606 throw OrthancException(ErrorCode_NotImplemented); | |
607 } | |
608 };break; | |
609 default: | |
610 throw OrthancException(ErrorCode_NotImplemented); | |
611 } | |
612 } | |
613 | |
614 | |
615 void ImageProcessing::Convert(ImageAccessor& target, | |
616 const ImageAccessor& source) | |
617 { | |
618 if (target.GetWidth() != source.GetWidth() || | |
619 target.GetHeight() != source.GetHeight()) | |
620 { | |
621 throw OrthancException(ErrorCode_IncompatibleImageSize); | |
622 } | |
623 | |
624 const unsigned int width = source.GetWidth(); | |
625 const unsigned int height = source.GetHeight(); | |
626 | |
627 if (source.GetFormat() == target.GetFormat()) | |
628 { | |
629 Copy(target, source); | |
630 return; | |
631 } | |
632 | |
633 if (target.GetFormat() == PixelFormat_Grayscale16 && | |
634 source.GetFormat() == PixelFormat_Grayscale8) | |
635 { | |
636 ConvertInternal<uint16_t, uint8_t>(target, source); | |
637 return; | |
638 } | |
639 | |
640 if (target.GetFormat() == PixelFormat_SignedGrayscale16 && | |
641 source.GetFormat() == PixelFormat_Grayscale8) | |
642 { | |
643 ConvertInternal<int16_t, uint8_t>(target, source); | |
644 return; | |
645 } | |
646 | |
647 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
648 source.GetFormat() == PixelFormat_Grayscale16) | |
649 { | |
650 ConvertInternal<uint8_t, uint16_t>(target, source); | |
651 return; | |
652 } | |
653 | |
654 if (target.GetFormat() == PixelFormat_SignedGrayscale16 && | |
655 source.GetFormat() == PixelFormat_Grayscale16) | |
656 { | |
657 ConvertInternal<int16_t, uint16_t>(target, source); | |
658 return; | |
659 } | |
660 | |
661 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
662 source.GetFormat() == PixelFormat_SignedGrayscale16) | |
663 { | |
664 ConvertInternal<uint8_t, int16_t>(target, source); | |
665 return; | |
666 } | |
667 | |
668 if (target.GetFormat() == PixelFormat_Grayscale16 && | |
669 source.GetFormat() == PixelFormat_SignedGrayscale16) | |
670 { | |
671 ConvertInternal<uint16_t, int16_t>(target, source); | |
672 return; | |
673 } | |
674 | |
675 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
676 source.GetFormat() == PixelFormat_RGB24) | |
677 { | |
678 ConvertColorToGrayscale<uint8_t>(target, source); | |
679 return; | |
680 } | |
681 | |
682 if (target.GetFormat() == PixelFormat_Grayscale16 && | |
683 source.GetFormat() == PixelFormat_RGB24) | |
684 { | |
685 ConvertColorToGrayscale<uint16_t>(target, source); | |
686 return; | |
687 } | |
688 | |
689 if (target.GetFormat() == PixelFormat_SignedGrayscale16 && | |
690 source.GetFormat() == PixelFormat_RGB24) | |
691 { | |
692 ConvertColorToGrayscale<int16_t>(target, source); | |
693 return; | |
694 } | |
695 | |
696 if (target.GetFormat() == PixelFormat_Float32 && | |
697 source.GetFormat() == PixelFormat_Grayscale8) | |
698 { | |
699 ConvertGrayscaleToFloat<uint8_t>(target, source); | |
700 return; | |
701 } | |
702 | |
703 if (target.GetFormat() == PixelFormat_Float32 && | |
704 source.GetFormat() == PixelFormat_Grayscale16) | |
705 { | |
706 ConvertGrayscaleToFloat<uint16_t>(target, source); | |
707 return; | |
708 } | |
709 | |
710 if (target.GetFormat() == PixelFormat_Float32 && | |
711 source.GetFormat() == PixelFormat_Grayscale32) | |
712 { | |
713 ConvertGrayscaleToFloat<uint32_t>(target, source); | |
714 return; | |
715 } | |
716 | |
717 if (target.GetFormat() == PixelFormat_Float32 && | |
718 source.GetFormat() == PixelFormat_SignedGrayscale16) | |
719 { | |
720 ConvertGrayscaleToFloat<int16_t>(target, source); | |
721 return; | |
722 } | |
723 | |
724 | |
725 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
726 source.GetFormat() == PixelFormat_RGBA32) | |
727 { | |
728 for (unsigned int y = 0; y < height; y++) | |
729 { | |
730 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
731 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
732 for (unsigned int x = 0; x < width; x++, q++) | |
733 { | |
734 *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) + | |
735 7152 * static_cast<uint32_t>(p[1]) + | |
736 0722 * static_cast<uint32_t>(p[2])) / 10000); | |
737 p += 4; | |
738 } | |
739 } | |
740 | |
741 return; | |
742 } | |
743 | |
744 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
745 source.GetFormat() == PixelFormat_BGRA32) | |
746 { | |
747 for (unsigned int y = 0; y < height; y++) | |
748 { | |
749 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
750 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
751 for (unsigned int x = 0; x < width; x++, q++) | |
752 { | |
753 *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[2]) + | |
754 7152 * static_cast<uint32_t>(p[1]) + | |
755 0722 * static_cast<uint32_t>(p[0])) / 10000); | |
756 p += 4; | |
757 } | |
758 } | |
759 | |
760 return; | |
761 } | |
762 | |
763 if (target.GetFormat() == PixelFormat_RGB24 && | |
764 source.GetFormat() == PixelFormat_RGBA32) | |
765 { | |
766 for (unsigned int y = 0; y < height; y++) | |
767 { | |
768 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
769 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
770 for (unsigned int x = 0; x < width; x++) | |
771 { | |
772 q[0] = p[0]; | |
773 q[1] = p[1]; | |
774 q[2] = p[2]; | |
775 p += 4; | |
776 q += 3; | |
777 } | |
778 } | |
779 | |
780 return; | |
781 } | |
782 | |
783 if (target.GetFormat() == PixelFormat_RGB24 && | |
784 source.GetFormat() == PixelFormat_BGRA32) | |
785 { | |
786 for (unsigned int y = 0; y < height; y++) | |
787 { | |
788 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
789 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
790 for (unsigned int x = 0; x < width; x++) | |
791 { | |
792 q[0] = p[2]; | |
793 q[1] = p[1]; | |
794 q[2] = p[0]; | |
795 p += 4; | |
796 q += 3; | |
797 } | |
798 } | |
799 | |
800 return; | |
801 } | |
802 | |
803 if (target.GetFormat() == PixelFormat_RGBA32 && | |
804 source.GetFormat() == PixelFormat_RGB24) | |
805 { | |
806 for (unsigned int y = 0; y < height; y++) | |
807 { | |
808 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
809 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
810 for (unsigned int x = 0; x < width; x++) | |
811 { | |
812 q[0] = p[0]; | |
813 q[1] = p[1]; | |
814 q[2] = p[2]; | |
815 q[3] = 255; // Set the alpha channel to full opacity | |
816 p += 3; | |
817 q += 4; | |
818 } | |
819 } | |
820 | |
821 return; | |
822 } | |
823 | |
824 if (target.GetFormat() == PixelFormat_RGB24 && | |
825 source.GetFormat() == PixelFormat_Grayscale8) | |
826 { | |
827 for (unsigned int y = 0; y < height; y++) | |
828 { | |
829 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
830 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
831 for (unsigned int x = 0; x < width; x++) | |
832 { | |
833 q[0] = *p; | |
834 q[1] = *p; | |
835 q[2] = *p; | |
836 p += 1; | |
837 q += 3; | |
838 } | |
839 } | |
840 | |
841 return; | |
842 } | |
843 | |
844 if ((target.GetFormat() == PixelFormat_RGBA32 || | |
845 target.GetFormat() == PixelFormat_BGRA32) && | |
846 source.GetFormat() == PixelFormat_Grayscale8) | |
847 { | |
848 for (unsigned int y = 0; y < height; y++) | |
849 { | |
850 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
851 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
852 for (unsigned int x = 0; x < width; x++) | |
853 { | |
854 q[0] = *p; | |
855 q[1] = *p; | |
856 q[2] = *p; | |
857 q[3] = 255; | |
858 p += 1; | |
859 q += 4; | |
860 } | |
861 } | |
862 | |
863 return; | |
864 } | |
865 | |
866 if (target.GetFormat() == PixelFormat_BGRA32 && | |
867 source.GetFormat() == PixelFormat_Grayscale16) | |
868 { | |
869 for (unsigned int y = 0; y < height; y++) | |
870 { | |
871 const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y)); | |
872 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
873 for (unsigned int x = 0; x < width; x++) | |
874 { | |
875 uint8_t value = (*p < 256 ? *p : 255); | |
876 q[0] = value; | |
877 q[1] = value; | |
878 q[2] = value; | |
879 q[3] = 255; | |
880 p += 1; | |
881 q += 4; | |
882 } | |
883 } | |
884 | |
885 return; | |
886 } | |
887 | |
888 if (target.GetFormat() == PixelFormat_BGRA32 && | |
889 source.GetFormat() == PixelFormat_SignedGrayscale16) | |
890 { | |
891 for (unsigned int y = 0; y < height; y++) | |
892 { | |
893 const int16_t* p = reinterpret_cast<const int16_t*>(source.GetConstRow(y)); | |
894 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
895 for (unsigned int x = 0; x < width; x++) | |
896 { | |
897 uint8_t value; | |
898 if (*p < 0) | |
899 { | |
900 value = 0; | |
901 } | |
902 else if (*p > 255) | |
903 { | |
904 value = 255; | |
905 } | |
906 else | |
907 { | |
908 value = static_cast<uint8_t>(*p); | |
909 } | |
910 | |
911 q[0] = value; | |
912 q[1] = value; | |
913 q[2] = value; | |
914 q[3] = 255; | |
915 p += 1; | |
916 q += 4; | |
917 } | |
918 } | |
919 | |
920 return; | |
921 } | |
922 | |
923 if (target.GetFormat() == PixelFormat_BGRA32 && | |
924 source.GetFormat() == PixelFormat_RGB24) | |
925 { | |
926 for (unsigned int y = 0; y < height; y++) | |
927 { | |
928 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
929 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
930 for (unsigned int x = 0; x < width; x++) | |
931 { | |
932 q[0] = p[2]; | |
933 q[1] = p[1]; | |
934 q[2] = p[0]; | |
935 q[3] = 255; | |
936 p += 3; | |
937 q += 4; | |
938 } | |
939 } | |
940 | |
941 return; | |
942 } | |
943 | |
944 if ((target.GetFormat() == PixelFormat_BGRA32 && | |
945 source.GetFormat() == PixelFormat_RGBA32) | |
946 || (target.GetFormat() == PixelFormat_RGBA32 && | |
947 source.GetFormat() == PixelFormat_BGRA32)) | |
948 { | |
949 for (unsigned int y = 0; y < height; y++) | |
950 { | |
951 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); | |
952 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
953 for (unsigned int x = 0; x < width; x++) | |
954 { | |
955 q[0] = p[2]; | |
956 q[1] = p[1]; | |
957 q[2] = p[0]; | |
958 q[3] = p[3]; | |
959 p += 4; | |
960 q += 4; | |
961 } | |
962 } | |
963 | |
964 return; | |
965 } | |
966 | |
967 if (target.GetFormat() == PixelFormat_RGB24 && | |
968 source.GetFormat() == PixelFormat_RGB48) | |
969 { | |
970 for (unsigned int y = 0; y < height; y++) | |
971 { | |
972 const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y)); | |
973 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); | |
974 for (unsigned int x = 0; x < width; x++) | |
975 { | |
976 q[0] = p[0] >> 8; | |
977 q[1] = p[1] >> 8; | |
978 q[2] = p[2] >> 8; | |
979 p += 3; | |
980 q += 3; | |
981 } | |
982 } | |
983 | |
984 return; | |
985 } | |
986 | |
987 if (target.GetFormat() == PixelFormat_Grayscale16 && | |
988 source.GetFormat() == PixelFormat_Float32) | |
989 { | |
990 ConvertFloatToGrayscale<PixelFormat_Grayscale16>(target, source); | |
991 return; | |
992 } | |
993 | |
994 if (target.GetFormat() == PixelFormat_Grayscale8 && | |
995 source.GetFormat() == PixelFormat_Float32) | |
996 { | |
997 ConvertFloatToGrayscale<PixelFormat_Grayscale8>(target, source); | |
998 return; | |
999 } | |
1000 | |
1001 throw OrthancException(ErrorCode_NotImplemented); | |
1002 } | |
1003 | |
1004 | |
1005 | |
1006 void ImageProcessing::Set(ImageAccessor& image, | |
1007 int64_t value) | |
1008 { | |
1009 switch (image.GetFormat()) | |
1010 { | |
1011 case PixelFormat_Grayscale8: | |
1012 SetInternal<uint8_t>(image, value); | |
1013 return; | |
1014 | |
1015 case PixelFormat_Grayscale16: | |
1016 SetInternal<uint16_t>(image, value); | |
1017 return; | |
1018 | |
1019 case PixelFormat_Grayscale32: | |
1020 SetInternal<uint32_t>(image, value); | |
1021 return; | |
1022 | |
1023 case PixelFormat_Grayscale64: | |
1024 SetInternal<uint64_t>(image, value); | |
1025 return; | |
1026 | |
1027 case PixelFormat_SignedGrayscale16: | |
1028 SetInternal<int16_t>(image, value); | |
1029 return; | |
1030 | |
1031 case PixelFormat_Float32: | |
1032 assert(sizeof(float) == 4); | |
1033 SetInternal<float>(image, value); | |
1034 return; | |
1035 | |
1036 case PixelFormat_RGBA32: | |
1037 case PixelFormat_BGRA32: | |
1038 case PixelFormat_RGB24: | |
1039 { | |
1040 uint8_t v = static_cast<uint8_t>(value); | |
1041 Set(image, v, v, v, v); // Use the color version | |
1042 return; | |
1043 } | |
1044 | |
1045 default: | |
1046 throw OrthancException(ErrorCode_NotImplemented); | |
1047 } | |
1048 } | |
1049 | |
1050 | |
1051 void ImageProcessing::Set(ImageAccessor& image, | |
1052 uint8_t red, | |
1053 uint8_t green, | |
1054 uint8_t blue, | |
1055 uint8_t alpha) | |
1056 { | |
1057 uint8_t p[4]; | |
1058 unsigned int size; | |
1059 | |
1060 switch (image.GetFormat()) | |
1061 { | |
1062 case PixelFormat_RGBA32: | |
1063 p[0] = red; | |
1064 p[1] = green; | |
1065 p[2] = blue; | |
1066 p[3] = alpha; | |
1067 size = 4; | |
1068 break; | |
1069 | |
1070 case PixelFormat_BGRA32: | |
1071 p[0] = blue; | |
1072 p[1] = green; | |
1073 p[2] = red; | |
1074 p[3] = alpha; | |
1075 size = 4; | |
1076 break; | |
1077 | |
1078 case PixelFormat_RGB24: | |
1079 p[0] = red; | |
1080 p[1] = green; | |
1081 p[2] = blue; | |
1082 size = 3; | |
1083 break; | |
1084 | |
1085 default: | |
1086 throw OrthancException(ErrorCode_NotImplemented); | |
1087 } | |
1088 | |
1089 const unsigned int width = image.GetWidth(); | |
1090 const unsigned int height = image.GetHeight(); | |
1091 | |
1092 for (unsigned int y = 0; y < height; y++) | |
1093 { | |
1094 uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y)); | |
1095 | |
1096 for (unsigned int x = 0; x < width; x++) | |
1097 { | |
1098 for (unsigned int i = 0; i < size; i++) | |
1099 { | |
1100 q[i] = p[i]; | |
1101 } | |
1102 | |
1103 q += size; | |
1104 } | |
1105 } | |
1106 } | |
1107 | |
1108 void ImageProcessing::Set(ImageAccessor& image, | |
1109 uint8_t red, | |
1110 uint8_t green, | |
1111 uint8_t blue, | |
1112 ImageAccessor& alpha) | |
1113 { | |
1114 uint8_t p[4]; | |
1115 | |
1116 if (alpha.GetWidth() != image.GetWidth() || alpha.GetHeight() != image.GetHeight()) | |
1117 { | |
1118 throw OrthancException(ErrorCode_IncompatibleImageSize); | |
1119 } | |
1120 | |
1121 if (alpha.GetFormat() != PixelFormat_Grayscale8) | |
1122 { | |
1123 throw OrthancException(ErrorCode_NotImplemented); | |
1124 } | |
1125 | |
1126 switch (image.GetFormat()) | |
1127 { | |
1128 case PixelFormat_RGBA32: | |
1129 p[0] = red; | |
1130 p[1] = green; | |
1131 p[2] = blue; | |
1132 break; | |
1133 | |
1134 case PixelFormat_BGRA32: | |
1135 p[0] = blue; | |
1136 p[1] = green; | |
1137 p[2] = red; | |
1138 break; | |
1139 | |
1140 default: | |
1141 throw OrthancException(ErrorCode_NotImplemented); | |
1142 } | |
1143 | |
1144 const unsigned int width = image.GetWidth(); | |
1145 const unsigned int height = image.GetHeight(); | |
1146 | |
1147 for (unsigned int y = 0; y < height; y++) | |
1148 { | |
1149 uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y)); | |
1150 uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y)); | |
1151 | |
1152 for (unsigned int x = 0; x < width; x++) | |
1153 { | |
1154 for (unsigned int i = 0; i < 3; i++) | |
1155 { | |
1156 q[i] = p[i]; | |
1157 } | |
1158 q[3] = *a; | |
1159 q += 4; | |
1160 ++a; | |
1161 } | |
1162 } | |
1163 } | |
1164 | |
1165 | |
1166 void ImageProcessing::ShiftRight(ImageAccessor& image, | |
1167 unsigned int shift) | |
1168 { | |
1169 if (image.GetWidth() == 0 || | |
1170 image.GetHeight() == 0 || | |
1171 shift == 0) | |
1172 { | |
1173 // Nothing to do | |
1174 return; | |
1175 } | |
1176 | |
1177 switch (image.GetFormat()) | |
1178 { | |
1179 case PixelFormat_Grayscale8: | |
1180 { | |
1181 ShiftRightInternal<uint8_t>(image, shift); | |
1182 break; | |
1183 } | |
1184 | |
1185 case PixelFormat_Grayscale16: | |
1186 { | |
1187 ShiftRightInternal<uint16_t>(image, shift); | |
1188 break; | |
1189 } | |
1190 default: | |
1191 throw OrthancException(ErrorCode_NotImplemented); | |
1192 } | |
1193 } | |
1194 | |
1195 void ImageProcessing::ShiftLeft(ImageAccessor& image, | |
1196 unsigned int shift) | |
1197 { | |
1198 if (image.GetWidth() == 0 || | |
1199 image.GetHeight() == 0 || | |
1200 shift == 0) | |
1201 { | |
1202 // Nothing to do | |
1203 return; | |
1204 } | |
1205 | |
1206 switch (image.GetFormat()) | |
1207 { | |
1208 case PixelFormat_Grayscale8: | |
1209 { | |
1210 ShiftLeftInternal<uint8_t>(image, shift); | |
1211 break; | |
1212 } | |
1213 | |
1214 case PixelFormat_Grayscale16: | |
1215 { | |
1216 ShiftLeftInternal<uint16_t>(image, shift); | |
1217 break; | |
1218 } | |
1219 default: | |
1220 throw OrthancException(ErrorCode_NotImplemented); | |
1221 } | |
1222 } | |
1223 | |
1224 void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue, | |
1225 int64_t& maxValue, | |
1226 const ImageAccessor& image) | |
1227 { | |
1228 switch (image.GetFormat()) | |
1229 { | |
1230 case PixelFormat_Grayscale8: | |
1231 { | |
1232 uint8_t a, b; | |
1233 GetMinMaxValueInternal<uint8_t>(a, b, image); | |
1234 minValue = a; | |
1235 maxValue = b; | |
1236 break; | |
1237 } | |
1238 | |
1239 case PixelFormat_Grayscale16: | |
1240 { | |
1241 uint16_t a, b; | |
1242 GetMinMaxValueInternal<uint16_t>(a, b, image); | |
1243 minValue = a; | |
1244 maxValue = b; | |
1245 break; | |
1246 } | |
1247 | |
1248 case PixelFormat_Grayscale32: | |
1249 { | |
1250 uint32_t a, b; | |
1251 GetMinMaxValueInternal<uint32_t>(a, b, image); | |
1252 minValue = a; | |
1253 maxValue = b; | |
1254 break; | |
1255 } | |
1256 | |
1257 case PixelFormat_SignedGrayscale16: | |
1258 { | |
1259 int16_t a, b; | |
1260 GetMinMaxValueInternal<int16_t>(a, b, image); | |
1261 minValue = a; | |
1262 maxValue = b; | |
1263 break; | |
1264 } | |
1265 | |
1266 default: | |
1267 throw OrthancException(ErrorCode_NotImplemented); | |
1268 } | |
1269 } | |
1270 | |
1271 | |
1272 void ImageProcessing::GetMinMaxFloatValue(float& minValue, | |
1273 float& maxValue, | |
1274 const ImageAccessor& image) | |
1275 { | |
1276 switch (image.GetFormat()) | |
1277 { | |
1278 case PixelFormat_Float32: | |
1279 { | |
1280 assert(sizeof(float) == 4); | |
1281 float a, b; | |
1282 | |
1283 /** | |
1284 * WARNING - On floating-point types, the minimal value is | |
1285 * "-FLT_MAX" (as implemented by "::lowest()"), not "FLT_MIN" | |
1286 * (as implemented by "::min()") | |
1287 * https://en.cppreference.com/w/cpp/types/numeric_limits | |
1288 **/ | |
1289 GetMinMaxValueInternal<float>(a, b, image, -std::numeric_limits<float>::max()); | |
1290 minValue = a; | |
1291 maxValue = b; | |
1292 break; | |
1293 } | |
1294 | |
1295 default: | |
1296 throw OrthancException(ErrorCode_NotImplemented); | |
1297 } | |
1298 } | |
1299 | |
1300 | |
1301 | |
1302 void ImageProcessing::AddConstant(ImageAccessor& image, | |
1303 int64_t value) | |
1304 { | |
1305 switch (image.GetFormat()) | |
1306 { | |
1307 case PixelFormat_Grayscale8: | |
1308 AddConstantInternal<uint8_t>(image, value); | |
1309 return; | |
1310 | |
1311 case PixelFormat_Grayscale16: | |
1312 AddConstantInternal<uint16_t>(image, value); | |
1313 return; | |
1314 | |
1315 case PixelFormat_SignedGrayscale16: | |
1316 AddConstantInternal<int16_t>(image, value); | |
1317 return; | |
1318 | |
1319 default: | |
1320 throw OrthancException(ErrorCode_NotImplemented); | |
1321 } | |
1322 } | |
1323 | |
1324 | |
1325 void ImageProcessing::MultiplyConstant(ImageAccessor& image, | |
1326 float factor, | |
1327 bool useRound) | |
1328 { | |
1329 switch (image.GetFormat()) | |
1330 { | |
1331 case PixelFormat_Grayscale8: | |
1332 if (useRound) | |
1333 { | |
1334 MultiplyConstantInternal<uint8_t, true>(image, factor); | |
1335 } | |
1336 else | |
1337 { | |
1338 MultiplyConstantInternal<uint8_t, false>(image, factor); | |
1339 } | |
1340 return; | |
1341 | |
1342 case PixelFormat_Grayscale16: | |
1343 if (useRound) | |
1344 { | |
1345 MultiplyConstantInternal<uint16_t, true>(image, factor); | |
1346 } | |
1347 else | |
1348 { | |
1349 MultiplyConstantInternal<uint16_t, false>(image, factor); | |
1350 } | |
1351 return; | |
1352 | |
1353 case PixelFormat_SignedGrayscale16: | |
1354 if (useRound) | |
1355 { | |
1356 MultiplyConstantInternal<int16_t, true>(image, factor); | |
1357 } | |
1358 else | |
1359 { | |
1360 MultiplyConstantInternal<int16_t, false>(image, factor); | |
1361 } | |
1362 return; | |
1363 | |
1364 default: | |
1365 throw OrthancException(ErrorCode_NotImplemented); | |
1366 } | |
1367 } | |
1368 | |
1369 | |
1370 void ImageProcessing::ShiftScale(ImageAccessor& image, | |
1371 float offset, | |
1372 float scaling, | |
1373 bool useRound) | |
1374 { | |
1375 // Rewrite "(x + offset) * scaling" as "a * x + b" | |
1376 | |
1377 const float a = scaling; | |
1378 const float b = offset * scaling; | |
1379 | |
1380 switch (image.GetFormat()) | |
1381 { | |
1382 case PixelFormat_Grayscale8: | |
1383 if (useRound) | |
1384 { | |
1385 ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min()); | |
1386 } | |
1387 else | |
1388 { | |
1389 ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min()); | |
1390 } | |
1391 return; | |
1392 | |
1393 case PixelFormat_Grayscale16: | |
1394 if (useRound) | |
1395 { | |
1396 ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min()); | |
1397 } | |
1398 else | |
1399 { | |
1400 ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min()); | |
1401 } | |
1402 return; | |
1403 | |
1404 case PixelFormat_SignedGrayscale16: | |
1405 if (useRound) | |
1406 { | |
1407 ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min()); | |
1408 } | |
1409 else | |
1410 { | |
1411 ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min()); | |
1412 } | |
1413 return; | |
1414 | |
1415 case PixelFormat_Float32: | |
1416 // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double. | |
1417 if (useRound) | |
1418 { | |
1419 ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max()); | |
1420 } | |
1421 else | |
1422 { | |
1423 ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max()); | |
1424 } | |
1425 return; | |
1426 | |
1427 default: | |
1428 throw OrthancException(ErrorCode_NotImplemented); | |
1429 } | |
1430 } | |
1431 | |
1432 | |
1433 void ImageProcessing::ShiftScale(ImageAccessor& target, | |
1434 const ImageAccessor& source, | |
1435 float offset, | |
1436 float scaling, | |
1437 bool useRound) | |
1438 { | |
1439 // Rewrite "(x + offset) * scaling" as "a * x + b" | |
1440 | |
1441 const float a = scaling; | |
1442 const float b = offset * scaling; | |
1443 | |
1444 switch (target.GetFormat()) | |
1445 { | |
1446 case PixelFormat_Grayscale8: | |
1447 | |
1448 switch (source.GetFormat()) | |
1449 { | |
1450 case PixelFormat_Float32: | |
1451 if (useRound) | |
1452 { | |
1453 ShiftScaleInternal<uint8_t, float, true, false>( | |
1454 target, source, a, b, std::numeric_limits<uint8_t>::min()); | |
1455 } | |
1456 else | |
1457 { | |
1458 ShiftScaleInternal<uint8_t, float, false, false>( | |
1459 target, source, a, b, std::numeric_limits<uint8_t>::min()); | |
1460 } | |
1461 return; | |
1462 | |
1463 default: | |
1464 throw OrthancException(ErrorCode_NotImplemented); | |
1465 } | |
1466 | |
1467 default: | |
1468 throw OrthancException(ErrorCode_NotImplemented); | |
1469 } | |
1470 } | |
1471 | |
1472 | |
1473 void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue) | |
1474 { | |
1475 const unsigned int width = image.GetWidth(); | |
1476 const unsigned int height = image.GetHeight(); | |
1477 | |
1478 switch (image.GetFormat()) | |
1479 { | |
1480 case PixelFormat_Grayscale16: | |
1481 { | |
1482 uint16_t maxValueUint16 = (uint16_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint16_t>::max()))); | |
1483 | |
1484 for (unsigned int y = 0; y < height; y++) | |
1485 { | |
1486 uint16_t* p = reinterpret_cast<uint16_t*>(image.GetRow(y)); | |
1487 | |
1488 for (unsigned int x = 0; x < width; x++, p++) | |
1489 { | |
1490 *p = maxValueUint16 - (*p); | |
1491 } | |
1492 } | |
1493 | |
1494 return; | |
1495 } | |
1496 case PixelFormat_Grayscale8: | |
1497 { | |
1498 uint8_t maxValueUint8 = (uint8_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint8_t>::max()))); | |
1499 | |
1500 for (unsigned int y = 0; y < height; y++) | |
1501 { | |
1502 uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); | |
1503 | |
1504 for (unsigned int x = 0; x < width; x++, p++) | |
1505 { | |
1506 *p = maxValueUint8 - (*p); | |
1507 } | |
1508 } | |
1509 | |
1510 return; | |
1511 } | |
1512 | |
1513 default: | |
1514 throw OrthancException(ErrorCode_NotImplemented); | |
1515 } | |
1516 | |
1517 } | |
1518 | |
1519 void ImageProcessing::Invert(ImageAccessor& image) | |
1520 { | |
1521 switch (image.GetFormat()) | |
1522 { | |
1523 case PixelFormat_Grayscale8: | |
1524 return Invert(image, 255); | |
1525 default: | |
1526 throw OrthancException(ErrorCode_NotImplemented); // you should use the Invert(image, maxValue) overload | |
1527 } | |
1528 } | |
1529 | |
1530 | |
1531 | |
1532 namespace | |
1533 { | |
1534 template <Orthanc::PixelFormat Format> | |
1535 class BresenhamPixelWriter | |
1536 { | |
1537 private: | |
1538 typedef typename PixelTraits<Format>::PixelType PixelType; | |
1539 | |
1540 Orthanc::ImageAccessor& image_; | |
1541 PixelType value_; | |
1542 | |
1543 void PlotLineLow(int x0, | |
1544 int y0, | |
1545 int x1, | |
1546 int y1) | |
1547 { | |
1548 int dx = x1 - x0; | |
1549 int dy = y1 - y0; | |
1550 int yi = 1; | |
1551 | |
1552 if (dy < 0) | |
1553 { | |
1554 yi = -1; | |
1555 dy = -dy; | |
1556 } | |
1557 | |
1558 int d = 2 * dy - dx; | |
1559 int y = y0; | |
1560 | |
1561 for (int x = x0; x <= x1; x++) | |
1562 { | |
1563 Write(x, y); | |
1564 | |
1565 if (d > 0) | |
1566 { | |
1567 y = y + yi; | |
1568 d = d - 2 * dx; | |
1569 } | |
1570 | |
1571 d = d + 2*dy; | |
1572 } | |
1573 } | |
1574 | |
1575 void PlotLineHigh(int x0, | |
1576 int y0, | |
1577 int x1, | |
1578 int y1) | |
1579 { | |
1580 int dx = x1 - x0; | |
1581 int dy = y1 - y0; | |
1582 int xi = 1; | |
1583 | |
1584 if (dx < 0) | |
1585 { | |
1586 xi = -1; | |
1587 dx = -dx; | |
1588 } | |
1589 | |
1590 int d = 2 * dx - dy; | |
1591 int x = x0; | |
1592 | |
1593 for (int y = y0; y <= y1; y++) | |
1594 { | |
1595 Write(x, y); | |
1596 | |
1597 if (d > 0) | |
1598 { | |
1599 x = x + xi; | |
1600 d = d - 2 * dy; | |
1601 } | |
1602 | |
1603 d = d + 2 * dx; | |
1604 } | |
1605 } | |
1606 | |
1607 public: | |
1608 BresenhamPixelWriter(Orthanc::ImageAccessor& image, | |
1609 int64_t value) : | |
1610 image_(image), | |
1611 value_(PixelTraits<Format>::IntegerToPixel(value)) | |
1612 { | |
1613 } | |
1614 | |
1615 BresenhamPixelWriter(Orthanc::ImageAccessor& image, | |
1616 const PixelType& value) : | |
1617 image_(image), | |
1618 value_(value) | |
1619 { | |
1620 } | |
1621 | |
1622 void Write(int x, | |
1623 int y) | |
1624 { | |
1625 if (x >= 0 && | |
1626 y >= 0 && | |
1627 static_cast<unsigned int>(x) < image_.GetWidth() && | |
1628 static_cast<unsigned int>(y) < image_.GetHeight()) | |
1629 { | |
1630 PixelType* p = reinterpret_cast<PixelType*>(image_.GetRow(y)); | |
1631 p[x] = value_; | |
1632 } | |
1633 } | |
1634 | |
1635 void DrawSegment(int x0, | |
1636 int y0, | |
1637 int x1, | |
1638 int y1) | |
1639 { | |
1640 // This is an implementation of Bresenham's line algorithm | |
1641 // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases | |
1642 | |
1643 if (abs(y1 - y0) < abs(x1 - x0)) | |
1644 { | |
1645 if (x0 > x1) | |
1646 { | |
1647 PlotLineLow(x1, y1, x0, y0); | |
1648 } | |
1649 else | |
1650 { | |
1651 PlotLineLow(x0, y0, x1, y1); | |
1652 } | |
1653 } | |
1654 else | |
1655 { | |
1656 if (y0 > y1) | |
1657 { | |
1658 PlotLineHigh(x1, y1, x0, y0); | |
1659 } | |
1660 else | |
1661 { | |
1662 PlotLineHigh(x0, y0, x1, y1); | |
1663 } | |
1664 } | |
1665 } | |
1666 }; | |
1667 } | |
1668 | |
1669 | |
1670 void ImageProcessing::DrawLineSegment(ImageAccessor& image, | |
1671 int x0, | |
1672 int y0, | |
1673 int x1, | |
1674 int y1, | |
1675 int64_t value) | |
1676 { | |
1677 switch (image.GetFormat()) | |
1678 { | |
1679 case Orthanc::PixelFormat_Grayscale8: | |
1680 { | |
1681 BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value); | |
1682 writer.DrawSegment(x0, y0, x1, y1); | |
1683 break; | |
1684 } | |
1685 | |
1686 case Orthanc::PixelFormat_Grayscale16: | |
1687 { | |
1688 BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value); | |
1689 writer.DrawSegment(x0, y0, x1, y1); | |
1690 break; | |
1691 } | |
1692 | |
1693 case Orthanc::PixelFormat_SignedGrayscale16: | |
1694 { | |
1695 BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value); | |
1696 writer.DrawSegment(x0, y0, x1, y1); | |
1697 break; | |
1698 } | |
1699 | |
1700 default: | |
1701 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1702 } | |
1703 } | |
1704 | |
1705 | |
1706 void ImageProcessing::DrawLineSegment(ImageAccessor& image, | |
1707 int x0, | |
1708 int y0, | |
1709 int x1, | |
1710 int y1, | |
1711 uint8_t red, | |
1712 uint8_t green, | |
1713 uint8_t blue, | |
1714 uint8_t alpha) | |
1715 { | |
1716 switch (image.GetFormat()) | |
1717 { | |
1718 case Orthanc::PixelFormat_BGRA32: | |
1719 { | |
1720 PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel; | |
1721 pixel.red_ = red; | |
1722 pixel.green_ = green; | |
1723 pixel.blue_ = blue; | |
1724 pixel.alpha_ = alpha; | |
1725 | |
1726 BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel); | |
1727 writer.DrawSegment(x0, y0, x1, y1); | |
1728 break; | |
1729 } | |
1730 | |
1731 case Orthanc::PixelFormat_RGBA32: | |
1732 { | |
1733 PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel; | |
1734 pixel.red_ = red; | |
1735 pixel.green_ = green; | |
1736 pixel.blue_ = blue; | |
1737 pixel.alpha_ = alpha; | |
1738 | |
1739 BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel); | |
1740 writer.DrawSegment(x0, y0, x1, y1); | |
1741 break; | |
1742 } | |
1743 | |
1744 case Orthanc::PixelFormat_RGB24: | |
1745 { | |
1746 PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel; | |
1747 pixel.red_ = red; | |
1748 pixel.green_ = green; | |
1749 pixel.blue_ = blue; | |
1750 | |
1751 BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel); | |
1752 writer.DrawSegment(x0, y0, x1, y1); | |
1753 break; | |
1754 } | |
1755 | |
1756 default: | |
1757 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1758 } | |
1759 } | |
1760 | |
1761 void ComputePolygonExtent(int32_t& left, int32_t& right, int32_t& top, int32_t& bottom, const std::vector<ImageProcessing::ImagePoint>& points) | |
1762 { | |
1763 left = std::numeric_limits<int32_t>::max(); | |
1764 right = std::numeric_limits<int32_t>::min(); | |
1765 top = std::numeric_limits<int32_t>::max(); | |
1766 bottom = std::numeric_limits<int32_t>::min(); | |
1767 | |
1768 for (size_t i = 0; i < points.size(); i++) | |
1769 { | |
1770 const ImageProcessing::ImagePoint& p = points[i]; | |
1771 left = std::min(p.GetX(), left); | |
1772 right = std::max(p.GetX(), right); | |
1773 bottom = std::max(p.GetY(), bottom); | |
1774 top = std::min(p.GetY(), top); | |
1775 } | |
1776 } | |
1777 | |
1778 template <PixelFormat TargetFormat> | |
1779 void FillPolygon_(ImageAccessor& image, | |
1780 const std::vector<ImageProcessing::ImagePoint>& points, | |
1781 int64_t value_) | |
1782 { | |
1783 typedef typename PixelTraits<TargetFormat>::PixelType TargetType; | |
1784 | |
1785 TargetType value = PixelTraits<TargetFormat>::IntegerToPixel(value_); | |
1786 int imageWidth = static_cast<int>(image.GetWidth()); | |
1787 int imageHeight = static_cast<int>(image.GetHeight()); | |
1788 int32_t left; | |
1789 int32_t right; | |
1790 int32_t top; | |
1791 int32_t bottom; | |
1792 | |
1793 // TODO: test clipping in UT (in Trello board) | |
1794 ComputePolygonExtent(left, right, top, bottom, points); | |
1795 | |
1796 // clip the computed extent with the target image | |
1797 // L and R | |
1798 left = std::max(0, left); | |
1799 left = std::min(imageWidth, left); | |
1800 right = std::max(0, right); | |
1801 right = std::min(imageWidth, right); | |
1802 if (left > right) | |
1803 std::swap(left, right); | |
1804 | |
1805 // T and B | |
1806 top = std::max(0, top); | |
1807 top = std::min(imageHeight, top); | |
1808 bottom = std::max(0, bottom); | |
1809 bottom = std::min(imageHeight, bottom); | |
1810 if (top > bottom) | |
1811 std::swap(top, bottom); | |
1812 | |
1813 // from http://alienryderflex.com/polygon_fill/ | |
1814 | |
1815 // convert all "corner" points to double only once | |
1816 std::vector<double> cpx; | |
1817 std::vector<double> cpy; | |
1818 size_t cpSize = points.size(); | |
1819 for (size_t i = 0; i < points.size(); i++) | |
1820 { | |
1821 if (points[i].GetX() < 0 || points[i].GetX() >= imageWidth | |
1822 || points[i].GetY() < 0 || points[i].GetY() >= imageHeight) | |
1823 { | |
1824 throw Orthanc::OrthancException(ErrorCode_ParameterOutOfRange); | |
1825 } | |
1826 cpx.push_back((double)points[i].GetX()); | |
1827 cpy.push_back((double)points[i].GetY()); | |
1828 } | |
1829 | |
1830 // Draw the lines segments | |
1831 for (size_t i = 0; i < (points.size() -1); i++) | |
1832 { | |
1833 ImageProcessing::DrawLineSegment(image, points[i].GetX(), points[i].GetY(), points[i+1].GetX(), points[i+1].GetY(), value_); | |
1834 } | |
1835 ImageProcessing::DrawLineSegment(image, points[points.size() -1].GetX(), points[points.size() -1].GetY(), points[0].GetX(), points[0].GetY(), value_); | |
1836 | |
1837 std::vector<int32_t> nodeX; | |
1838 nodeX.resize(cpSize); | |
1839 int nodes, pixelX, pixelY, i, j, swap ; | |
1840 | |
1841 // Loop through the rows of the image. | |
1842 for (pixelY = top; pixelY < bottom; pixelY++) | |
1843 { | |
1844 double y = (double)pixelY; | |
1845 // Build a list of nodes. | |
1846 nodes = 0; | |
1847 j = static_cast<int>(cpSize) - 1; | |
1848 | |
1849 for (i = 0; i < static_cast<int>(cpSize); i++) | |
1850 { | |
1851 if ((cpy[i] < y && cpy[j] >= y) || (cpy[j] < y && cpy[i] >= y)) | |
1852 { | |
1853 nodeX[nodes++] = (int32_t)(cpx[i] + (y - cpy[i])/(cpy[j] - cpy[i]) * (cpx[j] - cpx[i])); | |
1854 } | |
1855 j=i; | |
1856 } | |
1857 | |
1858 // Sort the nodes, via a simple “Bubble” sort. | |
1859 i=0; | |
1860 while (i < nodes-1) | |
1861 { | |
1862 if (nodeX[i] > nodeX[i+1]) | |
1863 { | |
1864 swap = nodeX[i]; | |
1865 nodeX[i] = nodeX[i+1]; | |
1866 nodeX[i+1] = swap; | |
1867 if (i > 0) | |
1868 { | |
1869 i--; | |
1870 } | |
1871 } | |
1872 else | |
1873 { | |
1874 i++; | |
1875 } | |
1876 } | |
1877 | |
1878 TargetType* row = reinterpret_cast<TargetType*>(image.GetRow(pixelY)); | |
1879 // Fill the pixels between node pairs. | |
1880 for (i = 0; i < nodes; i += 2) | |
1881 { | |
1882 if (nodeX[i] >= right) | |
1883 break; | |
1884 | |
1885 if (nodeX[i + 1] >= left) | |
1886 { | |
1887 if (nodeX[i] < left) | |
1888 { | |
1889 nodeX[i] = left; | |
1890 } | |
1891 | |
1892 if (nodeX[i + 1] > right) | |
1893 { | |
1894 nodeX[i + 1] = right; | |
1895 } | |
1896 | |
1897 for (pixelX = nodeX[i]; pixelX <= nodeX[i + 1]; pixelX++) | |
1898 { | |
1899 *(row + pixelX) = value; | |
1900 } | |
1901 } | |
1902 } | |
1903 } | |
1904 } | |
1905 | |
1906 void ImageProcessing::FillPolygon(ImageAccessor& image, | |
1907 const std::vector<ImagePoint>& points, | |
1908 int64_t value) | |
1909 { | |
1910 switch (image.GetFormat()) | |
1911 { | |
1912 case Orthanc::PixelFormat_Grayscale8: | |
1913 { | |
1914 FillPolygon_<Orthanc::PixelFormat_Grayscale8>(image, points, value); | |
1915 break; | |
1916 } | |
1917 case Orthanc::PixelFormat_Grayscale16: | |
1918 { | |
1919 FillPolygon_<Orthanc::PixelFormat_Grayscale16>(image, points, value); | |
1920 break; | |
1921 } | |
1922 case Orthanc::PixelFormat_SignedGrayscale16: | |
1923 { | |
1924 FillPolygon_<Orthanc::PixelFormat_SignedGrayscale16>(image, points, value); | |
1925 break; | |
1926 } | |
1927 default: | |
1928 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1929 } | |
1930 } | |
1931 | |
1932 | |
1933 template <PixelFormat Format> | |
1934 static void ResizeInternal(ImageAccessor& target, | |
1935 const ImageAccessor& source) | |
1936 { | |
1937 assert(target.GetFormat() == source.GetFormat() && | |
1938 target.GetFormat() == Format); | |
1939 | |
1940 const unsigned int sourceWidth = source.GetWidth(); | |
1941 const unsigned int sourceHeight = source.GetHeight(); | |
1942 const unsigned int targetWidth = target.GetWidth(); | |
1943 const unsigned int targetHeight = target.GetHeight(); | |
1944 | |
1945 if (targetWidth == 0 || targetHeight == 0) | |
1946 { | |
1947 return; | |
1948 } | |
1949 | |
1950 if (sourceWidth == 0 || sourceHeight == 0) | |
1951 { | |
1952 // Avoids division by zero below | |
1953 ImageProcessing::Set(target, 0); | |
1954 return; | |
1955 } | |
1956 | |
1957 const float scaleX = static_cast<float>(sourceWidth) / static_cast<float>(targetWidth); | |
1958 const float scaleY = static_cast<float>(sourceHeight) / static_cast<float>(targetHeight); | |
1959 | |
1960 | |
1961 /** | |
1962 * Create two lookup tables to quickly know the (x,y) position | |
1963 * in the source image, given the (x,y) position in the target | |
1964 * image. | |
1965 **/ | |
1966 | |
1967 std::vector<unsigned int> lookupX(targetWidth); | |
1968 | |
1969 for (unsigned int x = 0; x < targetWidth; x++) | |
1970 { | |
1971 int sourceX = static_cast<int>(std::floor((static_cast<float>(x) + 0.5f) * scaleX)); | |
1972 if (sourceX < 0) | |
1973 { | |
1974 sourceX = 0; // Should never happen | |
1975 } | |
1976 else if (sourceX >= static_cast<int>(sourceWidth)) | |
1977 { | |
1978 sourceX = sourceWidth - 1; | |
1979 } | |
1980 | |
1981 lookupX[x] = static_cast<unsigned int>(sourceX); | |
1982 } | |
1983 | |
1984 std::vector<unsigned int> lookupY(targetHeight); | |
1985 | |
1986 for (unsigned int y = 0; y < targetHeight; y++) | |
1987 { | |
1988 int sourceY = static_cast<int>(std::floor((static_cast<float>(y) + 0.5f) * scaleY)); | |
1989 if (sourceY < 0) | |
1990 { | |
1991 sourceY = 0; // Should never happen | |
1992 } | |
1993 else if (sourceY >= static_cast<int>(sourceHeight)) | |
1994 { | |
1995 sourceY = sourceHeight - 1; | |
1996 } | |
1997 | |
1998 lookupY[y] = static_cast<unsigned int>(sourceY); | |
1999 } | |
2000 | |
2001 | |
2002 /** | |
2003 * Actual resizing | |
2004 **/ | |
2005 | |
2006 for (unsigned int targetY = 0; targetY < targetHeight; targetY++) | |
2007 { | |
2008 unsigned int sourceY = lookupY[targetY]; | |
2009 | |
2010 for (unsigned int targetX = 0; targetX < targetWidth; targetX++) | |
2011 { | |
2012 unsigned int sourceX = lookupX[targetX]; | |
2013 | |
2014 typename ImageTraits<Format>::PixelType pixel; | |
2015 ImageTraits<Format>::GetPixel(pixel, source, sourceX, sourceY); | |
2016 ImageTraits<Format>::SetPixel(target, pixel, targetX, targetY); | |
2017 } | |
2018 } | |
2019 } | |
2020 | |
2021 | |
2022 | |
2023 void ImageProcessing::Resize(ImageAccessor& target, | |
2024 const ImageAccessor& source) | |
2025 { | |
2026 if (source.GetFormat() != source.GetFormat()) | |
2027 { | |
2028 throw OrthancException(ErrorCode_IncompatibleImageFormat); | |
2029 } | |
2030 | |
2031 if (source.GetWidth() == target.GetWidth() && | |
2032 source.GetHeight() == target.GetHeight()) | |
2033 { | |
2034 Copy(target, source); | |
2035 return; | |
2036 } | |
2037 | |
2038 switch (source.GetFormat()) | |
2039 { | |
2040 case PixelFormat_Grayscale8: | |
2041 ResizeInternal<PixelFormat_Grayscale8>(target, source); | |
2042 break; | |
2043 | |
2044 case PixelFormat_Float32: | |
2045 ResizeInternal<PixelFormat_Float32>(target, source); | |
2046 break; | |
2047 | |
2048 case PixelFormat_RGB24: | |
2049 ResizeInternal<PixelFormat_RGB24>(target, source); | |
2050 break; | |
2051 | |
2052 default: | |
2053 throw OrthancException(ErrorCode_NotImplemented); | |
2054 } | |
2055 } | |
2056 | |
2057 | |
2058 ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source, | |
2059 bool forceMinimalPitch) | |
2060 { | |
2061 std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2, | |
2062 source.GetHeight() / 2, forceMinimalPitch)); | |
2063 Resize(*target, source); | |
2064 return target.release(); | |
2065 } | |
2066 | |
2067 | |
2068 template <PixelFormat Format> | |
2069 static void FlipXInternal(ImageAccessor& image) | |
2070 { | |
2071 const unsigned int height = image.GetHeight(); | |
2072 const unsigned int width = image.GetWidth(); | |
2073 | |
2074 for (unsigned int y = 0; y < height; y++) | |
2075 { | |
2076 for (unsigned int x1 = 0; x1 < width / 2; x1++) | |
2077 { | |
2078 unsigned int x2 = width - 1 - x1; | |
2079 | |
2080 typename ImageTraits<Format>::PixelType a, b; | |
2081 ImageTraits<Format>::GetPixel(a, image, x1, y); | |
2082 ImageTraits<Format>::GetPixel(b, image, x2, y); | |
2083 ImageTraits<Format>::SetPixel(image, a, x2, y); | |
2084 ImageTraits<Format>::SetPixel(image, b, x1, y); | |
2085 } | |
2086 } | |
2087 } | |
2088 | |
2089 | |
2090 void ImageProcessing::FlipX(ImageAccessor& image) | |
2091 { | |
2092 switch (image.GetFormat()) | |
2093 { | |
2094 case PixelFormat_Grayscale8: | |
2095 FlipXInternal<PixelFormat_Grayscale8>(image); | |
2096 break; | |
2097 | |
2098 case PixelFormat_RGB24: | |
2099 FlipXInternal<PixelFormat_RGB24>(image); | |
2100 break; | |
2101 | |
2102 default: | |
2103 throw OrthancException(ErrorCode_NotImplemented); | |
2104 } | |
2105 } | |
2106 | |
2107 | |
2108 template <PixelFormat Format> | |
2109 static void FlipYInternal(ImageAccessor& image) | |
2110 { | |
2111 const unsigned int height = image.GetHeight(); | |
2112 const unsigned int width = image.GetWidth(); | |
2113 | |
2114 for (unsigned int y1 = 0; y1 < height / 2; y1++) | |
2115 { | |
2116 unsigned int y2 = height - 1 - y1; | |
2117 | |
2118 for (unsigned int x = 0; x < width; x++) | |
2119 { | |
2120 typename ImageTraits<Format>::PixelType a, b; | |
2121 ImageTraits<Format>::GetPixel(a, image, x, y1); | |
2122 ImageTraits<Format>::GetPixel(b, image, x, y2); | |
2123 ImageTraits<Format>::SetPixel(image, a, x, y2); | |
2124 ImageTraits<Format>::SetPixel(image, b, x, y1); | |
2125 } | |
2126 } | |
2127 } | |
2128 | |
2129 | |
2130 void ImageProcessing::FlipY(ImageAccessor& image) | |
2131 { | |
2132 switch (image.GetFormat()) | |
2133 { | |
2134 case PixelFormat_Grayscale8: | |
2135 FlipYInternal<PixelFormat_Grayscale8>(image); | |
2136 break; | |
2137 | |
2138 case PixelFormat_RGB24: | |
2139 FlipYInternal<PixelFormat_RGB24>(image); | |
2140 break; | |
2141 | |
2142 default: | |
2143 throw OrthancException(ErrorCode_NotImplemented); | |
2144 } | |
2145 } | |
2146 | |
2147 | |
2148 // This is a slow implementation of horizontal convolution on one | |
2149 // individual channel, that checks for out-of-image values | |
2150 template <typename RawPixel, unsigned int ChannelsCount> | |
2151 static float GetHorizontalConvolutionFloatSecure(const Orthanc::ImageAccessor& source, | |
2152 const std::vector<float>& horizontal, | |
2153 size_t horizontalAnchor, | |
2154 unsigned int x, | |
2155 unsigned int y, | |
2156 float leftBorder, | |
2157 float rightBorder, | |
2158 unsigned int channel) | |
2159 { | |
2160 const RawPixel* row = reinterpret_cast<const RawPixel*>(source.GetConstRow(y)) + channel; | |
2161 | |
2162 float p = 0; | |
2163 | |
2164 for (unsigned int k = 0; k < horizontal.size(); k++) | |
2165 { | |
2166 float value; | |
2167 | |
2168 if (x + k < horizontalAnchor) // Negation of "x - horizontalAnchor + k >= 0" | |
2169 { | |
2170 value = leftBorder; | |
2171 } | |
2172 else if (x + k >= source.GetWidth() + horizontalAnchor) // Negation of "x - horizontalAnchor + k < width" | |
2173 { | |
2174 value = rightBorder; | |
2175 } | |
2176 else | |
2177 { | |
2178 // The value lies within the image | |
2179 value = row[(x - horizontalAnchor + k) * ChannelsCount]; | |
2180 } | |
2181 | |
2182 p += value * horizontal[k]; | |
2183 } | |
2184 | |
2185 return p; | |
2186 } | |
2187 | |
2188 | |
2189 | |
2190 // This is an implementation of separable convolution that uses | |
2191 // floating-point arithmetics, and an intermediate Float32 | |
2192 // image. The out-of-image values are taken as the border | |
2193 // value. Further optimization is possible. | |
2194 template <typename RawPixel, unsigned int ChannelsCount> | |
2195 static void SeparableConvolutionFloat(ImageAccessor& image /* inplace */, | |
2196 const std::vector<float>& horizontal, | |
2197 size_t horizontalAnchor, | |
2198 const std::vector<float>& vertical, | |
2199 size_t verticalAnchor, | |
2200 float normalization) | |
2201 { | |
2202 // WARNING - "::min()" should be replaced by "::lowest()" if | |
2203 // dealing with float or double (which is not the case so far) | |
2204 assert(sizeof(RawPixel) <= 2); // Safeguard to remember about "float/double" | |
2205 | |
2206 const unsigned int width = image.GetWidth(); | |
2207 const unsigned int height = image.GetHeight(); | |
2208 | |
2209 | |
2210 /** | |
2211 * Horizontal convolution | |
2212 **/ | |
2213 | |
2214 Image tmp(PixelFormat_Float32, ChannelsCount * width, height, false); | |
2215 | |
2216 for (unsigned int y = 0; y < height; y++) | |
2217 { | |
2218 const RawPixel* row = reinterpret_cast<const RawPixel*>(image.GetConstRow(y)); | |
2219 | |
2220 float leftBorder[ChannelsCount], rightBorder[ChannelsCount]; | |
2221 | |
2222 for (unsigned int c = 0; c < ChannelsCount; c++) | |
2223 { | |
2224 leftBorder[c] = row[c]; | |
2225 rightBorder[c] = row[ChannelsCount * (width - 1) + c]; | |
2226 } | |
2227 | |
2228 float* p = static_cast<float*>(tmp.GetRow(y)); | |
2229 | |
2230 if (width < horizontal.size()) | |
2231 { | |
2232 // It is not possible to have the full kernel within the image, use the direct implementation | |
2233 for (unsigned int x = 0; x < width; x++) | |
2234 { | |
2235 for (unsigned int c = 0; c < ChannelsCount; c++, p++) | |
2236 { | |
2237 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount> | |
2238 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); | |
2239 } | |
2240 } | |
2241 } | |
2242 else | |
2243 { | |
2244 // Deal with the left border | |
2245 for (unsigned int x = 0; x < horizontalAnchor; x++) | |
2246 { | |
2247 for (unsigned int c = 0; c < ChannelsCount; c++, p++) | |
2248 { | |
2249 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount> | |
2250 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); | |
2251 } | |
2252 } | |
2253 | |
2254 // Deal with the central portion of the image (all pixel values | |
2255 // scanned by the kernel lie inside the image) | |
2256 | |
2257 for (unsigned int x = 0; x < width - horizontal.size() + 1; x++) | |
2258 { | |
2259 for (unsigned int c = 0; c < ChannelsCount; c++, p++) | |
2260 { | |
2261 *p = 0; | |
2262 for (unsigned int k = 0; k < horizontal.size(); k++) | |
2263 { | |
2264 *p += static_cast<float>(row[(x + k) * ChannelsCount + c]) * horizontal[k]; | |
2265 } | |
2266 } | |
2267 } | |
2268 | |
2269 // Deal with the right border | |
2270 for (unsigned int x = static_cast<unsigned int>( | |
2271 horizontalAnchor + width - horizontal.size() + 1); x < width; x++) | |
2272 { | |
2273 for (unsigned int c = 0; c < ChannelsCount; c++, p++) | |
2274 { | |
2275 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount> | |
2276 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); | |
2277 } | |
2278 } | |
2279 } | |
2280 } | |
2281 | |
2282 | |
2283 /** | |
2284 * Vertical convolution | |
2285 **/ | |
2286 | |
2287 std::vector<const float*> rows(vertical.size()); | |
2288 | |
2289 for (unsigned int y = 0; y < height; y++) | |
2290 { | |
2291 for (unsigned int k = 0; k < vertical.size(); k++) | |
2292 { | |
2293 if (y + k < verticalAnchor) | |
2294 { | |
2295 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(0)); // Use top border | |
2296 } | |
2297 else if (y + k >= height + verticalAnchor) | |
2298 { | |
2299 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(height - 1)); // Use bottom border | |
2300 } | |
2301 else | |
2302 { | |
2303 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(static_cast<unsigned int>(y + k - verticalAnchor))); | |
2304 } | |
2305 } | |
2306 | |
2307 RawPixel* p = reinterpret_cast<RawPixel*>(image.GetRow(y)); | |
2308 | |
2309 for (unsigned int x = 0; x < width; x++) | |
2310 { | |
2311 for (unsigned int c = 0; c < ChannelsCount; c++, p++) | |
2312 { | |
2313 float accumulator = 0; | |
2314 | |
2315 for (unsigned int k = 0; k < vertical.size(); k++) | |
2316 { | |
2317 accumulator += rows[k][ChannelsCount * x + c] * vertical[k]; | |
2318 } | |
2319 | |
2320 accumulator *= normalization; | |
2321 | |
2322 if (accumulator <= static_cast<float>(std::numeric_limits<RawPixel>::min())) | |
2323 { | |
2324 *p = std::numeric_limits<RawPixel>::min(); | |
2325 } | |
2326 else if (accumulator >= static_cast<float>(std::numeric_limits<RawPixel>::max())) | |
2327 { | |
2328 *p = std::numeric_limits<RawPixel>::max(); | |
2329 } | |
2330 else | |
2331 { | |
2332 *p = static_cast<RawPixel>(accumulator); | |
2333 } | |
2334 } | |
2335 } | |
2336 } | |
2337 } | |
2338 | |
2339 | |
2340 void ImageProcessing::SeparableConvolution(ImageAccessor& image /* inplace */, | |
2341 const std::vector<float>& horizontal, | |
2342 size_t horizontalAnchor, | |
2343 const std::vector<float>& vertical, | |
2344 size_t verticalAnchor) | |
2345 { | |
2346 if (horizontal.size() == 0 || | |
2347 vertical.size() == 0 || | |
2348 horizontalAnchor >= horizontal.size() || | |
2349 verticalAnchor >= vertical.size()) | |
2350 { | |
2351 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
2352 } | |
2353 | |
2354 if (image.GetWidth() == 0 || | |
2355 image.GetHeight() == 0) | |
2356 { | |
2357 return; | |
2358 } | |
2359 | |
2360 /** | |
2361 * Compute normalization | |
2362 **/ | |
2363 | |
2364 float sumHorizontal = 0; | |
2365 for (size_t i = 0; i < horizontal.size(); i++) | |
2366 { | |
2367 sumHorizontal += horizontal[i]; | |
2368 } | |
2369 | |
2370 float sumVertical = 0; | |
2371 for (size_t i = 0; i < vertical.size(); i++) | |
2372 { | |
2373 sumVertical += vertical[i]; | |
2374 } | |
2375 | |
2376 if (fabsf(sumHorizontal) <= std::numeric_limits<float>::epsilon() || | |
2377 fabsf(sumVertical) <= std::numeric_limits<float>::epsilon()) | |
2378 { | |
2379 throw OrthancException(ErrorCode_ParameterOutOfRange, "Singular convolution kernel"); | |
2380 } | |
2381 | |
2382 const float normalization = 1.0f / (sumHorizontal * sumVertical); | |
2383 | |
2384 switch (image.GetFormat()) | |
2385 { | |
2386 case PixelFormat_Grayscale8: | |
2387 SeparableConvolutionFloat<uint8_t, 1u> | |
2388 (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); | |
2389 break; | |
2390 | |
2391 case PixelFormat_RGB24: | |
2392 SeparableConvolutionFloat<uint8_t, 3u> | |
2393 (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); | |
2394 break; | |
2395 | |
2396 default: | |
2397 throw OrthancException(ErrorCode_NotImplemented); | |
2398 } | |
2399 } | |
2400 | |
2401 | |
2402 void ImageProcessing::SmoothGaussian5x5(ImageAccessor& image) | |
2403 { | |
2404 std::vector<float> kernel(5); | |
2405 kernel[0] = 1; | |
2406 kernel[1] = 4; | |
2407 kernel[2] = 6; | |
2408 kernel[3] = 4; | |
2409 kernel[4] = 1; | |
2410 | |
2411 SeparableConvolution(image, kernel, 2, kernel, 2); | |
2412 } | |
2413 | |
2414 | |
2415 void ImageProcessing::FitSize(ImageAccessor& target, | |
2416 const ImageAccessor& source) | |
2417 { | |
2418 if (target.GetWidth() == 0 || | |
2419 target.GetHeight() == 0) | |
2420 { | |
2421 return; | |
2422 } | |
2423 | |
2424 if (source.GetWidth() == target.GetWidth() && | |
2425 source.GetHeight() == target.GetHeight()) | |
2426 { | |
2427 Copy(target, source); | |
2428 return; | |
2429 } | |
2430 | |
2431 Set(target, 0); | |
2432 | |
2433 // Preserve the aspect ratio | |
2434 float cw = static_cast<float>(source.GetWidth()); | |
2435 float ch = static_cast<float>(source.GetHeight()); | |
2436 float r = std::min( | |
2437 static_cast<float>(target.GetWidth()) / cw, | |
2438 static_cast<float>(target.GetHeight()) / ch); | |
2439 | |
2440 unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth()); | |
2441 unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight()); | |
2442 Image resized(target.GetFormat(), sw, sh, false); | |
2443 | |
2444 //ImageProcessing::SmoothGaussian5x5(source); | |
2445 ImageProcessing::Resize(resized, source); | |
2446 | |
2447 assert(target.GetWidth() >= resized.GetWidth() && | |
2448 target.GetHeight() >= resized.GetHeight()); | |
2449 unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2; | |
2450 unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2; | |
2451 | |
2452 ImageAccessor region; | |
2453 target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight()); | |
2454 ImageProcessing::Copy(region, resized); | |
2455 } | |
2456 | |
2457 | |
2458 ImageAccessor* ImageProcessing::FitSize(const ImageAccessor& source, | |
2459 unsigned int width, | |
2460 unsigned int height) | |
2461 { | |
2462 std::unique_ptr<ImageAccessor> target(new Image(source.GetFormat(), width, height, false)); | |
2463 FitSize(*target, source); | |
2464 return target.release(); | |
2465 } | |
2466 } |