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