1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2005-05-25
7 * Description : border threaded image filter.
8 *
9 * Copyright 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 * Copyright 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11 * Copyright 2009-2010 by Andi Clemens <andi dot clemens at gmail dot com>
12 * Copyright 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
13 *
14 * This program is free software; you can redistribute it
15 * and/or modify it under the terms of the GNU General
16 * Public License as published by the Free Software Foundation;
17 * either version 2, or (at your option)
18 * any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * ============================================================ */
26
27 #include "borderfilter.h"
28
29 // C++ includes
30
31 #include <cmath>
32 #include <cstdlib>
33
34 // Qt includes
35
36 #include <QPoint>
37 #include <QPolygon>
38 #include <QRegion>
39
40 // KDE includes
41
42 #include <klocalizedstring.h>
43
44 // Local includes
45
46 #include "dimg.h"
47 #include "digikam_debug.h"
48
49 namespace Digikam
50 {
51
52 class Q_DECL_HIDDEN BorderFilter::Private
53 {
54 public:
55
Private()56 explicit Private()
57 : borderMainWidth(0),
58 border2ndWidth (0),
59 orgRatio (0.0f)
60 {
61 }
62
63 int borderMainWidth;
64 int border2ndWidth;
65
66 float orgRatio;
67
68 DColor solidColor;
69 DColor niepceBorderColor;
70 DColor niepceLineColor;
71 DColor bevelUpperLeftColor;
72 DColor bevelLowerRightColor;
73 DColor decorativeFirstColor;
74 DColor decorativeSecondColor;
75
76 BorderContainer settings;
77
78 public:
79
80 void setup(const DImg& m_orgImage);
81 };
82
setup(const DImg & m_orgImage)83 void BorderFilter::Private::setup(const DImg& m_orgImage)
84 {
85 solidColor = DColor(settings.solidColor, m_orgImage.sixteenBit());
86 niepceBorderColor = DColor(settings.niepceBorderColor, m_orgImage.sixteenBit());
87 niepceLineColor = DColor(settings.niepceLineColor, m_orgImage.sixteenBit());
88 bevelUpperLeftColor = DColor(settings.bevelUpperLeftColor, m_orgImage.sixteenBit());
89 bevelLowerRightColor = DColor(settings.bevelLowerRightColor, m_orgImage.sixteenBit());
90 decorativeFirstColor = DColor(settings.decorativeFirstColor, m_orgImage.sixteenBit());
91 decorativeSecondColor = DColor(settings.decorativeSecondColor, m_orgImage.sixteenBit());
92
93 if (settings.preserveAspectRatio)
94 {
95 orgRatio = (float)settings.orgWidth / (float)settings.orgHeight;
96 int size = qMin(m_orgImage.height(), m_orgImage.width());
97 borderMainWidth = (int)(size * settings.borderPercent);
98 border2ndWidth = (int)(size * 0.005);
99
100 // Clamp internal border with to 1 pixel to be visible with small image.
101
102 if (border2ndWidth < 1)
103 {
104 border2ndWidth = 1;
105 }
106 }
107 }
108
BorderFilter(QObject * const parent)109 BorderFilter::BorderFilter(QObject* const parent)
110 : DImgThreadedFilter(parent),
111 d (new Private)
112 {
113 initFilter();
114 }
115
BorderFilter(DImg * image,QObject * const parent,const BorderContainer & settings)116 BorderFilter::BorderFilter(DImg* image, QObject* const parent, const BorderContainer& settings)
117 : DImgThreadedFilter(image, parent, QLatin1String("Border")),
118 d (new Private)
119 {
120 d->settings = settings;
121 initFilter();
122 }
123
~BorderFilter()124 BorderFilter::~BorderFilter()
125 {
126 cancelFilter();
127 delete d;
128 }
129
DisplayableName()130 QString BorderFilter::DisplayableName()
131 {
132 return QString::fromUtf8(I18N_NOOP("Border Tool"));
133 }
134
filterImage()135 void BorderFilter::filterImage()
136 {
137 d->setup(m_orgImage);
138
139 switch (d->settings.borderType)
140 {
141 case BorderContainer::SolidBorder:
142
143 if (d->settings.preserveAspectRatio)
144 {
145 solid(m_orgImage, m_destImage, d->solidColor, d->borderMainWidth);
146 }
147 else
148 {
149 solid2(m_orgImage, m_destImage, d->solidColor, d->settings.borderWidth1);
150 }
151
152 break;
153
154 case BorderContainer::NiepceBorder:
155
156 if (d->settings.preserveAspectRatio)
157 {
158 niepce(m_orgImage, m_destImage, d->niepceBorderColor, d->borderMainWidth,
159 d->niepceLineColor, d->border2ndWidth);
160 }
161 else
162 {
163 niepce2(m_orgImage, m_destImage, d->niepceBorderColor, d->settings.borderWidth1,
164 d->niepceLineColor, d->settings.borderWidth4);
165 }
166
167 break;
168
169 case BorderContainer::BeveledBorder:
170
171 if (d->settings.preserveAspectRatio)
172 {
173 bevel(m_orgImage, m_destImage, d->bevelUpperLeftColor,
174 d->bevelLowerRightColor, d->borderMainWidth);
175 }
176 else
177 {
178 bevel2(m_orgImage, m_destImage, d->bevelUpperLeftColor,
179 d->bevelLowerRightColor, d->settings.borderWidth1);
180 }
181
182 break;
183
184 case BorderContainer::PineBorder:
185 case BorderContainer::WoodBorder:
186 case BorderContainer::PaperBorder:
187 case BorderContainer::ParqueBorder:
188 case BorderContainer::IceBorder:
189 case BorderContainer::LeafBorder:
190 case BorderContainer::MarbleBorder:
191 case BorderContainer::RainBorder:
192 case BorderContainer::CratersBorder:
193 case BorderContainer::DriedBorder:
194 case BorderContainer::PinkBorder:
195 case BorderContainer::StoneBorder:
196 case BorderContainer::ChalkBorder:
197 case BorderContainer::GraniteBorder:
198 case BorderContainer::RockBorder:
199 case BorderContainer::WallBorder:
200
201 if (d->settings.preserveAspectRatio)
202 {
203 pattern(m_orgImage, m_destImage, d->borderMainWidth,
204 d->decorativeFirstColor, d->decorativeSecondColor,
205 d->border2ndWidth, d->border2ndWidth);
206 }
207 else
208 {
209 pattern2(m_orgImage, m_destImage, d->settings.borderWidth1,
210 d->decorativeFirstColor, d->decorativeSecondColor,
211 d->settings.borderWidth2, d->settings.borderWidth2);
212 }
213
214 break;
215 }
216 }
217
218 // -- Methods to preserve aspect ratio of image ------------------------------------------
219
solid(DImg & src,DImg & dest,const DColor & fg,int borderWidth)220 void BorderFilter::solid(DImg& src, DImg& dest, const DColor& fg, int borderWidth)
221 {
222 if (d->settings.orgWidth > d->settings.orgHeight)
223 {
224 int height = src.height() + borderWidth * 2;
225 dest = DImg((int)(height * d->orgRatio), height, src.sixteenBit(), src.hasAlpha());
226 dest.fill(fg);
227 dest.bitBltImage(&src, (dest.width() - src.width()) / 2, borderWidth);
228 }
229 else
230 {
231 int width = src.width() + borderWidth * 2;
232 dest = DImg(width, (int)(width / d->orgRatio), src.sixteenBit(), src.hasAlpha());
233 dest.fill(fg);
234 dest.bitBltImage(&src, borderWidth, (dest.height() - src.height()) / 2);
235 }
236 }
237
niepce(DImg & src,DImg & dest,const DColor & fg,int borderWidth,const DColor & bg,int lineWidth)238 void BorderFilter::niepce(DImg& src, DImg& dest, const DColor& fg,
239 int borderWidth, const DColor& bg, int lineWidth)
240 {
241 DImg tmp;
242 solid(src, tmp, bg, lineWidth);
243 solid(tmp, dest, fg, borderWidth);
244 }
245
bevel(DImg & src,DImg & dest,const DColor & topColor,const DColor & btmColor,int borderWidth)246 void BorderFilter::bevel(DImg& src, DImg& dest, const DColor& topColor,
247 const DColor& btmColor, int borderWidth)
248 {
249 int width, height;
250
251 if (d->settings.orgWidth > d->settings.orgHeight)
252 {
253 height = src.height() + borderWidth * 2;
254 width = (int)(height * d->orgRatio);
255 }
256 else
257 {
258 width = src.width() + borderWidth * 2;
259 height = (int)(width / d->orgRatio);
260 }
261
262 dest = DImg(width, height, src.sixteenBit(), src.hasAlpha());
263 dest.fill(topColor);
264
265 QPolygon btTriangle(3);
266 btTriangle.setPoint(0, width, 0);
267 btTriangle.setPoint(1, 0, height);
268 btTriangle.setPoint(2, width, height);
269 QRegion btRegion(btTriangle);
270
271 // paint upper right corner
272
273 QPoint upperRightCorner((width - ((width - src.width()) / 2) - 2),
274 ((0 + (height - src.height())) / 2 + 2));
275
276 for (int x = upperRightCorner.x() ; x < width ; ++x)
277 {
278 for (int y = 0 ; y < upperRightCorner.y() ; ++y)
279 {
280 if (btRegion.contains(QPoint(x, y)))
281 {
282 dest.setPixelColor(x, y, btmColor);
283 }
284 }
285 }
286
287 // paint right border
288
289 for (int x = upperRightCorner.x() ; x < width ; ++x)
290 {
291 for (int y = upperRightCorner.y() ; y < height ; ++y)
292 {
293 dest.setPixelColor(x, y, btmColor);
294 }
295 }
296
297 // paint lower left corner
298
299 QPoint lowerLeftCorner((0 + ((width - src.width()) / 2) + 2),
300 (height - ((height - src.height()) / 2) - 2));
301
302 for (int x = 0 ; x < lowerLeftCorner.x() ; ++x)
303 {
304 for (int y = lowerLeftCorner.y() ; y < height ; ++y)
305 {
306 if (btRegion.contains(QPoint(x, y)))
307 {
308 dest.setPixelColor(x, y, btmColor);
309 }
310 }
311 }
312
313 // paint bottom border
314
315 for (int x = lowerLeftCorner.x() ; x < width ; ++x)
316 {
317 for (int y = lowerLeftCorner.y() ; y < height ; ++y)
318 {
319 dest.setPixelColor(x, y, btmColor);
320 }
321 }
322
323 if (d->settings.orgWidth > d->settings.orgHeight)
324 {
325 dest.bitBltImage(&src, (dest.width() - src.width()) / 2, borderWidth);
326 }
327 else
328 {
329 dest.bitBltImage(&src, borderWidth, (dest.height() - src.height()) / 2);
330 }
331 }
332
pattern(DImg & src,DImg & dest,int borderWidth,const DColor & firstColor,const DColor & secondColor,int firstWidth,int secondWidth)333 void BorderFilter::pattern(DImg& src, DImg& dest, int borderWidth,
334 const DColor& firstColor, const DColor& secondColor,
335 int firstWidth, int secondWidth)
336 {
337 // Original image with the second solid border around.
338
339 DImg tmp;
340 solid(src, tmp, secondColor, secondWidth);
341
342 // Border tiled image using pattern with second solid border around.
343
344 int width, height, destW, destH;
345
346 if (d->settings.orgWidth > d->settings.orgHeight)
347 {
348 height = d->settings.orgHeight + borderWidth * 2;
349 width = (int)(height * d->orgRatio);
350
351 destH = src.height() + borderWidth * 2;
352 destW = (int)(destH * d->orgRatio);
353 }
354 else
355 {
356 width = d->settings.orgWidth + borderWidth * 2;
357 height = (int)(width / d->orgRatio);
358
359 destW = src.width() + borderWidth * 2;
360 destH = (int)(destW / d->orgRatio);
361 }
362
363 DImg tmp2(width, height, tmp.sixteenBit(), tmp.hasAlpha());
364 qCDebug(DIGIKAM_DIMG_LOG) << "Border File:" << d->settings.borderPath;
365 DImg border(d->settings.borderPath);
366
367 if (border.isNull())
368 {
369 return;
370 }
371
372 border.convertToDepthOfImage(&tmp2);
373
374 for (int x = 0 ; x < width ; x += border.width())
375 {
376 for (int y = 0 ; y < height ; y += border.height())
377 {
378 tmp2.bitBltImage(&border, x, y);
379 }
380 }
381
382 tmp2 = tmp2.smoothScale(destW, destH);
383
384 solid(tmp2, dest, firstColor, firstWidth);
385
386 // Merge both images to one.
387
388 if (d->settings.orgWidth > d->settings.orgHeight)
389 {
390 dest.bitBltImage(&tmp, (dest.width() - tmp.width()) / 2, borderWidth);
391 }
392 else
393 {
394 dest.bitBltImage(&tmp, borderWidth, (dest.height() - tmp.height()) / 2);
395 }
396 }
397
398 // -- Methods to not-preserve aspect ratio of image ------------------------------------------
399
solid2(DImg & src,DImg & dest,const DColor & fg,int borderWidth)400 void BorderFilter::solid2(DImg& src, DImg& dest, const DColor& fg, int borderWidth)
401 {
402 dest = DImg(src.width() + borderWidth * 2, src.height() + borderWidth * 2,
403 src.sixteenBit(), src.hasAlpha());
404 dest.fill(fg);
405 dest.bitBltImage(&src, borderWidth, borderWidth);
406 }
407
niepce2(DImg & src,DImg & dest,const DColor & fg,int borderWidth,const DColor & bg,int lineWidth)408 void BorderFilter::niepce2(DImg& src, DImg& dest, const DColor& fg, int borderWidth,
409 const DColor& bg, int lineWidth)
410 {
411 DImg tmp;
412 solid2(src, tmp, bg, lineWidth);
413 solid2(tmp, dest, fg, borderWidth);
414 }
415
bevel2(DImg & src,DImg & dest,const DColor & topColor,const DColor & btmColor,int borderWidth)416 void BorderFilter::bevel2(DImg& src, DImg& dest, const DColor& topColor,
417 const DColor& btmColor, int borderWidth)
418 {
419 int x, y;
420 int wc;
421
422 dest = DImg(src.width() + borderWidth * 2,
423 src.height() + borderWidth * 2,
424 src.sixteenBit(), src.hasAlpha());
425
426 // top
427
428 for (y = 0, wc = (int)dest.width() - 1 ; y < borderWidth ; ++y, --wc)
429 {
430 for (x = 0; x < wc; ++x)
431 {
432 dest.setPixelColor(x, y, topColor);
433 }
434
435 for ( ; x < (int)dest.width() ; ++x)
436 {
437 dest.setPixelColor(x, y, btmColor);
438 }
439 }
440
441 // left and right
442
443 for ( ; y < (int)dest.height() - borderWidth ; ++y)
444 {
445 for (x = 0 ; x < borderWidth ; ++x)
446 {
447 dest.setPixelColor(x, y, topColor);
448 }
449
450 for (x = (int)dest.width() - 1 ; x > (int)dest.width() - borderWidth - 1 ; --x)
451 {
452 dest.setPixelColor(x, y, btmColor);
453 }
454 }
455
456 // bottom
457
458 for (wc = borderWidth ; y < (int)dest.height() ; ++y, --wc)
459 {
460 for (x = 0 ; x < wc ; ++x)
461 {
462 dest.setPixelColor(x, y, topColor);
463 }
464
465 for ( ; x < (int)dest.width() ; ++x)
466 {
467 dest.setPixelColor(x, y, btmColor);
468 }
469 }
470
471 dest.bitBltImage(&src, borderWidth, borderWidth);
472 }
473
pattern2(DImg & src,DImg & dest,int borderWidth,const DColor & firstColor,const DColor & secondColor,int firstWidth,int secondWidth)474 void BorderFilter::pattern2(DImg& src, DImg& dest, int borderWidth,
475 const DColor& firstColor, const DColor& secondColor,
476 int firstWidth, int secondWidth)
477 {
478 // Border tile.
479
480 int w = d->settings.orgWidth + borderWidth * 2;
481 int h = d->settings.orgHeight + borderWidth * 2;
482
483 qCDebug(DIGIKAM_DIMG_LOG) << "Border File:" << d->settings.borderPath;
484 DImg border(d->settings.borderPath);
485
486 if (border.isNull())
487 {
488 return;
489 }
490
491 DImg borderImg(w, h, src.sixteenBit(), src.hasAlpha());
492 border.convertToDepthOfImage(&borderImg);
493
494 for (int x = 0 ; x < w ; x += border.width())
495 {
496 for (int y = 0 ; y < h ; y += border.height())
497 {
498 borderImg.bitBltImage(&border, x, y);
499 }
500 }
501
502 // First line around the pattern tile.
503
504 DImg tmp = borderImg.smoothScale(src.width() + borderWidth * 2,
505 src.height() + borderWidth * 2);
506
507 solid2(tmp, dest, firstColor, firstWidth);
508
509 // Second line around original image.
510
511 tmp.reset();
512 solid2(src, tmp, secondColor, secondWidth);
513
514 // Copy original image.
515
516 dest.bitBltImage(&tmp, borderWidth, borderWidth);
517 }
518
colorToString(const QColor & c)519 static QString colorToString(const QColor& c)
520 {
521 if (c.alpha() != 255)
522 {
523 return QString::fromLatin1("rgb(%1,%2,%3)").arg(c.red()).arg(c.green()).arg(c.blue());
524 }
525 else
526 {
527 return QString::fromLatin1("rgba(%1,%2,%3,%4)").arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
528 }
529 }
530
stringToColor(const QString & s)531 static QColor stringToColor(const QString& s)
532 {
533 QRegExp regexp(QLatin1String("(rgb|rgba)\\s*\\((.+)\\)\\s*"));
534
535 if (regexp.exactMatch(s))
536 {
537 QStringList colors = regexp.cap(1).split(QLatin1Char(','), QString::SkipEmptyParts);
538
539 if (colors.size() >= 3)
540 {
541 QColor c(colors.at(0).toInt(), colors.at(1).toInt(), colors.at(2).toInt());
542
543 if (regexp.cap(0) == QLatin1String("rgba") && colors.size() == 4)
544 {
545 c.setAlpha(colors.at(4).toInt());
546 }
547
548 return c;
549 }
550 }
551
552 return QColor();
553 }
554
filterAction()555 FilterAction BorderFilter::filterAction()
556 {
557 FilterAction action(FilterIdentifier(), CurrentVersion());
558 action.setDisplayableName(DisplayableName());
559
560 action.setParameter(QLatin1String("borderPath"), d->settings.borderPath);
561 action.setParameter(QLatin1String("borderPercent"), d->settings.borderPercent);
562 action.setParameter(QLatin1String("borderType"), d->settings.borderType);
563 action.setParameter(QLatin1String("borderWidth1"), d->settings.borderWidth1);
564 action.setParameter(QLatin1String("borderWidth2"), d->settings.borderWidth2);
565 action.setParameter(QLatin1String("borderWidth3"), d->settings.borderWidth3);
566 action.setParameter(QLatin1String("borderWidth4"), d->settings.borderWidth4);
567 action.setParameter(QLatin1String("preserveAspectRatio"), d->settings.preserveAspectRatio);
568 action.setParameter(QLatin1String("orgHeight"), d->settings.orgHeight);
569 action.setParameter(QLatin1String("orgWidth"), d->settings.orgWidth);
570
571 action.setParameter(QLatin1String("solidColor"), colorToString(d->settings.solidColor));
572 action.setParameter(QLatin1String("niepceBorderColor"), colorToString(d->settings.niepceBorderColor));
573 action.setParameter(QLatin1String("niepceLineColor"), colorToString(d->settings.niepceLineColor));
574 action.setParameter(QLatin1String("bevelUpperLeftColor"), colorToString(d->settings.bevelUpperLeftColor));
575 action.setParameter(QLatin1String("bevelLowerRightColor"), colorToString(d->settings.bevelLowerRightColor));
576 action.setParameter(QLatin1String("decorativeFirstColor"), colorToString(d->settings.decorativeFirstColor));
577 action.setParameter(QLatin1String("decorativeSecondColor"), colorToString(d->settings.decorativeSecondColor));
578
579 return action;
580 }
581
readParameters(const FilterAction & action)582 void BorderFilter::readParameters(const FilterAction& action)
583 {
584 d->settings.borderPath = action.parameter(QLatin1String("borderPath")).toString();
585 d->settings.borderPercent = action.parameter(QLatin1String("borderPercent")).toDouble();
586 d->settings.borderType = action.parameter(QLatin1String("borderType")).toInt();
587 d->settings.borderWidth1 = action.parameter(QLatin1String("borderWidth1")).toInt();
588 d->settings.borderWidth2 = action.parameter(QLatin1String("borderWidth2")).toInt();
589 d->settings.borderWidth3 = action.parameter(QLatin1String("borderWidth3")).toInt();
590 d->settings.borderWidth4 = action.parameter(QLatin1String("borderWidth4")).toInt();
591 d->settings.preserveAspectRatio = action.parameter(QLatin1String("preserveAspectRatio")).toBool();
592 d->settings.orgHeight = action.parameter(QLatin1String("orgHeight")).toInt();
593 d->settings.orgWidth = action.parameter(QLatin1String("orgWidth")).toInt();
594
595 d->settings.solidColor = stringToColor(action.parameter(QLatin1String("solidColor")).toString());
596 d->settings.niepceBorderColor = stringToColor(action.parameter(QLatin1String("niepceBorderColor")).toString());
597 d->settings.niepceLineColor = stringToColor(action.parameter(QLatin1String("niepceLineColor")).toString());
598 d->settings.bevelUpperLeftColor = stringToColor(action.parameter(QLatin1String("bevelUpperLeftColor")).toString());
599 d->settings.bevelLowerRightColor = stringToColor(action.parameter(QLatin1String("bevelLowerRightColor")).toString());
600 d->settings.decorativeFirstColor = stringToColor(action.parameter(QLatin1String("decorativeFirstColor")).toString());
601 d->settings.decorativeSecondColor = stringToColor(action.parameter(QLatin1String("decorativeSecondColor")).toString());
602 }
603
604 } // namespace Digikam
605